diff -u -r -N squid-4.14/acinclude/os-deps.m4 squid-4.15/acinclude/os-deps.m4
--- squid-4.14/acinclude/os-deps.m4	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/acinclude/os-deps.m4	2021-05-10 14:04:15.000000000 +1200
@@ -792,6 +792,7 @@
   AC_CACHE_CHECK([for operational CPU clock access], 
                  squid_cv_cpu_profiler_works,
     AC_PREPROC_IFELSE([AC_LANG_SOURCE([[
+#include <ctime>
 #if defined(__GNUC__) && ( defined(__i386) || defined(__i386__) )
 // okay
 #elif defined(__GNUC__) && ( defined(__x86_64) || defined(__x86_64__) )
@@ -800,6 +801,8 @@
 // okay
 #elif defined(_M_IX86) && defined(_MSC_VER) /* x86 platform on Microsoft C Compiler ONLY */
 // okay
+#elif defined(HAVE_CLOCK_GETTIME_NSEC_NP) && defined(CLOCK_MONOTONIC_RAW)
+// okay
 #else
 #error This CPU is unsupported. No profiling available here.
 #endif
diff -u -r -N squid-4.14/ChangeLog squid-4.15/ChangeLog
--- squid-4.14/ChangeLog	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/ChangeLog	2021-05-10 14:04:15.000000000 +1200
@@ -1,3 +1,19 @@
+Changes in squid-4.15 (10 May 2021):
+
+	- Bug 5112: Excessively loud chunked reply parsing error reporting
+	- Bug 5106: Broken cache manager URL parsing
+	- Bug 5104: Memory leak in RFC 2169 response parsing
+	- Bug 3556: "FD ... is not an open socket" for accept() problems
+	- Profiling: CPU timing implemented for MAC non-x86
+	- Fix HttpHeaderStats definition to include hoErrorDetail
+	- Fix Squid-to-client write_timeout triggers client_lifetime timeout
+	- Limit HeaderLookupTable_t::lookup() to BadHdr and specific IDs
+	- Handle more Range requests
+	- Handle more partial responses
+	- Stop processing a response if the Store entry is gone
+	- ... and some portability fixes
+	- ... and some documentation updates
+
 Changes in squid-4.14 (02 Feb 2021):
 
 	- Regression Fix: support for non-lowercase Transfer-Encoding value
diff -u -r -N squid-4.14/configure squid-4.15/configure
--- squid-4.14/configure	2021-02-08 14:36:29.000000000 +1300
+++ squid-4.15/configure	2021-05-10 22:35:34.000000000 +1200
@@ -1,7 +1,7 @@
 #! /bin/sh
 # From configure.ac Revision.
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for Squid Web Proxy 4.14.
+# Generated by GNU Autoconf 2.69 for Squid Web Proxy 4.15.
 #
 # Report bugs to <http://bugs.squid-cache.org/>.
 #
@@ -595,8 +595,8 @@
 # Identity of this package.
 PACKAGE_NAME='Squid Web Proxy'
 PACKAGE_TARNAME='squid'
-PACKAGE_VERSION='4.14'
-PACKAGE_STRING='Squid Web Proxy 4.14'
+PACKAGE_VERSION='4.15'
+PACKAGE_STRING='Squid Web Proxy 4.15'
 PACKAGE_BUGREPORT='http://bugs.squid-cache.org/'
 PACKAGE_URL=''
 
@@ -1656,7 +1656,7 @@
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures Squid Web Proxy 4.14 to adapt to many kinds of systems.
+\`configure' configures Squid Web Proxy 4.15 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1727,7 +1727,7 @@
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of Squid Web Proxy 4.14:";;
+     short | recursive ) echo "Configuration of Squid Web Proxy 4.15:";;
    esac
   cat <<\_ACEOF
 
@@ -2166,7 +2166,7 @@
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-Squid Web Proxy configure 4.14
+Squid Web Proxy configure 4.15
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -3270,7 +3270,7 @@
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by Squid Web Proxy $as_me 4.14, which was
+It was created by Squid Web Proxy $as_me 4.15, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -4132,7 +4132,7 @@
 
 # Define the identity of the package.
  PACKAGE='squid'
- VERSION='4.14'
+ VERSION='4.15'
 
 
 cat >>confdefs.h <<_ACEOF
@@ -37412,6 +37412,17 @@
 
 # Default OFF. This is a debug feature. Only check and enable if forced ON.
 if test "x$enable_cpu_profiling" = "xyes"; then
+  for ac_func in clock_gettime_nsec_np
+do :
+  ac_fn_cxx_check_func "$LINENO" "clock_gettime_nsec_np" "ac_cv_func_clock_gettime_nsec_np"
+if test "x$ac_cv_func_clock_gettime_nsec_np" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_CLOCK_GETTIME_NSEC_NP 1
+_ACEOF
+
+fi
+done
+
 
   { $as_echo "$as_me:${as_lineno-$LINENO}: checking for operational CPU clock access" >&5
 $as_echo_n "checking for operational CPU clock access... " >&6; }
@@ -37421,6 +37432,7 @@
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
+#include <ctime>
 #if defined(__GNUC__) && ( defined(__i386) || defined(__i386__) )
 // okay
 #elif defined(__GNUC__) && ( defined(__x86_64) || defined(__x86_64__) )
@@ -37429,6 +37441,8 @@
 // okay
 #elif defined(_M_IX86) && defined(_MSC_VER) /* x86 platform on Microsoft C Compiler ONLY */
 // okay
+#elif defined(HAVE_CLOCK_GETTIME_NSEC_NP) && defined(CLOCK_MONOTONIC_RAW)
+// okay
 #else
 #error This CPU is unsupported. No profiling available here.
 #endif
@@ -44605,7 +44619,7 @@
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by Squid Web Proxy $as_me 4.14, which was
+This file was extended by Squid Web Proxy $as_me 4.15, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -44671,7 +44685,7 @@
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-Squid Web Proxy config.status 4.14
+Squid Web Proxy config.status 4.15
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
diff -u -r -N squid-4.14/configure.ac squid-4.15/configure.ac
--- squid-4.14/configure.ac	2021-02-08 14:36:29.000000000 +1300
+++ squid-4.15/configure.ac	2021-05-10 22:35:34.000000000 +1200
@@ -5,7 +5,7 @@
 ## Please see the COPYING and CONTRIBUTORS files for details.
 ##
 
-AC_INIT([Squid Web Proxy],[4.14],[http://bugs.squid-cache.org/],[squid])
+AC_INIT([Squid Web Proxy],[4.15],[http://bugs.squid-cache.org/],[squid])
 AC_PREREQ(2.61)
 AC_CONFIG_HEADERS([include/autoconf.h])
 AC_CONFIG_AUX_DIR(cfgaux)
@@ -2816,6 +2816,7 @@
 ])
 # Default OFF. This is a debug feature. Only check and enable if forced ON.
 if test "x$enable_cpu_profiling" = "xyes"; then
+  AC_CHECK_FUNCS(clock_gettime_nsec_np)
   SQUID_CHECK_FUNCTIONAL_CPU_PROFILER
   if test "x$squid_cv_cpu_profiler_works" = "xno"; then
     AC_MSG_ERROR([CPU profiling will not be functional in this build.])
diff -u -r -N squid-4.14/CONTRIBUTORS squid-4.15/CONTRIBUTORS
--- squid-4.14/CONTRIBUTORS	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/CONTRIBUTORS	2021-05-10 14:04:15.000000000 +1200
@@ -105,6 +105,7 @@
     Dan Searle <dan@censornet.com>
     Dan Searle <dan.searle@censornet.com>
     Dave Dykstra <dwd@fnal.gov>
+    David Carlier <devnexen@gmail.com>
     David Hill <david.hill@ubisoft.com>
     David Isaacs <david.isaacs@sbhs.nsw.edu.au>
     David J N Begley
@@ -250,6 +251,7 @@
     Jorge Ivan Burgos Aguilar <jorgeivanburgosaguilar@gmail.com>
     Jose Luis Godoy <joseluis.godoy@correo.aeat.es>
     Jose-Marcio Martins da Cruz <Jose-Marcio.Martins@mines-paristech.fr>
+    Joshua Rogers <megamansec@gmail.com>
     Joshua Root <jmr@macports.org>
     Joshua Root <josh+squid@root.id.au>
     JPP <jpp1@frws.com>
diff -u -r -N squid-4.14/doc/release-notes/release-4.html squid-4.15/doc/release-notes/release-4.html
--- squid-4.14/doc/release-notes/release-4.html	2021-02-08 14:41:40.000000000 +1300
+++ squid-4.15/doc/release-notes/release-4.html	2021-05-10 22:40:02.000000000 +1200
@@ -3,10 +3,10 @@
 <HEAD>
  <META NAME="GENERATOR" CONTENT="LinuxDoc-Tools 0.9.82">
  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
- <TITLE>Squid 4.14 release notes</TITLE>
+ <TITLE>Squid 4.15 release notes</TITLE>
 </HEAD>
 <BODY>
-<H1>Squid 4.14 release notes</H1>
+<H1>Squid 4.15 release notes</H1>
 
 <H2>Squid Developers</H2>
 <HR>
@@ -64,7 +64,7 @@
 <HR>
 <H2><A NAME="s1">1.</A> <A HREF="#toc1">Notice</A></H2>
 
-<P>The Squid Team are pleased to announce the release of Squid-4.14.</P>
+<P>The Squid Team are pleased to announce the release of Squid-4.15.</P>
 <P>This new release is available for download from 
 <A HREF="http://www.squid-cache.org/Versions/v4/">http://www.squid-cache.org/Versions/v4/</A> or the
 <A HREF="http://www.squid-cache.org/Download/http-mirrors.html">mirrors</A>.</P>
diff -u -r -N squid-4.14/errors/TRANSLATORS squid-4.15/errors/TRANSLATORS
--- squid-4.14/errors/TRANSLATORS	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/errors/TRANSLATORS	2021-05-10 14:04:15.000000000 +1200
@@ -109,7 +109,7 @@
 Russian			Andrew L. Davydov <davydov@okbmei.msk.su>
 Serbian			Zoran Verovski <zoran@hemofarm.co.yu>
 Serbian			Dragutin Cirkovic <auto@gromnet.net>
-Simplify Chinese	Wang DaQing <wdq@bigfoot.com>
+Simplified Chinese	Wang DaQing <wdq@bigfoot.com>
 Slovak			Peter Hanecak <hany@megaloman.sk>
 Spanish			Javier Puche <javier.puche@rediris.es>
 Spanish			Roberto Lumbreras <rover-squid@lander.es>
diff -u -r -N squid-4.14/include/autoconf.h.in squid-4.15/include/autoconf.h.in
--- squid-4.14/include/autoconf.h.in	2021-02-08 14:36:21.000000000 +1300
+++ squid-4.15/include/autoconf.h.in	2021-05-10 22:35:27.000000000 +1200
@@ -118,6 +118,9 @@
 /* Define to 1 if you have the <byteswap.h> header file. */
 #undef HAVE_BYTESWAP_H
 
+/* Define to 1 if you have the `clock_gettime_nsec_np' function. */
+#undef HAVE_CLOCK_GETTIME_NSEC_NP
+
 /* Define to 1 if you have the `closedir' function. */
 #undef HAVE_CLOSEDIR
 
diff -u -r -N squid-4.14/include/version.h squid-4.15/include/version.h
--- squid-4.14/include/version.h	2021-02-08 14:36:29.000000000 +1300
+++ squid-4.15/include/version.h	2021-05-10 22:35:34.000000000 +1200
@@ -7,7 +7,7 @@
  */
 
 #ifndef SQUID_RELEASE_TIME
-#define SQUID_RELEASE_TIME 1612748178
+#define SQUID_RELEASE_TIME 1620642924
 #endif
 
 /*
diff -u -r -N squid-4.14/lib/profiler/get_tick.h squid-4.15/lib/profiler/get_tick.h
--- squid-4.14/lib/profiler/get_tick.h	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/lib/profiler/get_tick.h	2021-05-10 14:04:15.000000000 +1200
@@ -10,6 +10,7 @@
 #define _PROFILER_GET_TICK_H_
 
 #if USE_XPROF_STATS
+#include <ctime>
 
 /*
  * Ensure that any changes here are synchronised with SQUID_CHECK_FUNCTIONAL_CPU_PROFILER
@@ -67,6 +68,14 @@
     return regs;
 }
 
+#elif defined(HAVE_CLOCK_GETTIME_NSEC_NP) && defined(CLOCK_MONOTONIC_RAW)
+
+static inline hrtime_t
+get_tick()
+{
+    return clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW);
+}
+
 #else
 /* This CPU is unsupported. Short-circuit, no profiling here */
 // #error for configure tests to prevent library construction
diff -u -r -N squid-4.14/RELEASENOTES.html squid-4.15/RELEASENOTES.html
--- squid-4.14/RELEASENOTES.html	2021-02-08 14:41:40.000000000 +1300
+++ squid-4.15/RELEASENOTES.html	2021-05-10 22:40:02.000000000 +1200
@@ -3,10 +3,10 @@
 <HEAD>
  <META NAME="GENERATOR" CONTENT="LinuxDoc-Tools 0.9.82">
  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
- <TITLE>Squid 4.14 release notes</TITLE>
+ <TITLE>Squid 4.15 release notes</TITLE>
 </HEAD>
 <BODY>
-<H1>Squid 4.14 release notes</H1>
+<H1>Squid 4.15 release notes</H1>
 
 <H2>Squid Developers</H2>
 <HR>
@@ -64,7 +64,7 @@
 <HR>
 <H2><A NAME="s1">1.</A> <A HREF="#toc1">Notice</A></H2>
 
-<P>The Squid Team are pleased to announce the release of Squid-4.14.</P>
+<P>The Squid Team are pleased to announce the release of Squid-4.15.</P>
 <P>This new release is available for download from 
 <A HREF="http://www.squid-cache.org/Versions/v4/">http://www.squid-cache.org/Versions/v4/</A> or the
 <A HREF="http://www.squid-cache.org/Download/http-mirrors.html">mirrors</A>.</P>
diff -u -r -N squid-4.14/src/acl/ConnMark.cc squid-4.15/src/acl/ConnMark.cc
--- squid-4.14/src/acl/ConnMark.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/acl/ConnMark.cc	2021-05-10 14:04:15.000000000 +1200
@@ -16,6 +16,8 @@
 #include "http/Stream.h"
 #include "sbuf/Stream.h"
 
+#include <limits>
+
 bool
 Acl::ConnMark::empty() const
 {
diff -u -r -N squid-4.14/src/acl/external/delayer/ext_delayer_acl.8 squid-4.15/src/acl/external/delayer/ext_delayer_acl.8
--- squid-4.14/src/acl/external/delayer/ext_delayer_acl.8	2021-02-08 14:41:43.000000000 +1300
+++ squid-4.15/src/acl/external/delayer/ext_delayer_acl.8	2021-05-10 22:40:06.000000000 +1200
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "EXT_DELAYER_ACL 8"
-.TH EXT_DELAYER_ACL 8 "2021-02-08" "perl v5.32.0" "User Contributed Perl Documentation"
+.TH EXT_DELAYER_ACL 8 "2021-05-10" "perl v5.32.0" "User Contributed Perl Documentation"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
diff -u -r -N squid-4.14/src/acl/external/SQL_session/ext_sql_session_acl.8 squid-4.15/src/acl/external/SQL_session/ext_sql_session_acl.8
--- squid-4.14/src/acl/external/SQL_session/ext_sql_session_acl.8	2021-02-08 14:41:43.000000000 +1300
+++ squid-4.15/src/acl/external/SQL_session/ext_sql_session_acl.8	2021-05-10 22:40:07.000000000 +1200
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "EXT_SQL_SESSION_ACL 8"
-.TH EXT_SQL_SESSION_ACL 8 "2021-02-08" "perl v5.32.0" "User Contributed Perl Documentation"
+.TH EXT_SQL_SESSION_ACL 8 "2021-05-10" "perl v5.32.0" "User Contributed Perl Documentation"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
diff -u -r -N squid-4.14/src/acl/external/wbinfo_group/ext_wbinfo_group_acl.8 squid-4.15/src/acl/external/wbinfo_group/ext_wbinfo_group_acl.8
--- squid-4.14/src/acl/external/wbinfo_group/ext_wbinfo_group_acl.8	2021-02-08 14:41:43.000000000 +1300
+++ squid-4.15/src/acl/external/wbinfo_group/ext_wbinfo_group_acl.8	2021-05-10 22:40:07.000000000 +1200
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "EXT_WBINFO_GROUP_ACL 8"
-.TH EXT_WBINFO_GROUP_ACL 8 "2021-02-08" "perl v5.32.0" "User Contributed Perl Documentation"
+.TH EXT_WBINFO_GROUP_ACL 8 "2021-05-10" "perl v5.32.0" "User Contributed Perl Documentation"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
diff -u -r -N squid-4.14/src/auth/basic/DB/basic_db_auth.8 squid-4.15/src/auth/basic/DB/basic_db_auth.8
--- squid-4.14/src/auth/basic/DB/basic_db_auth.8	2021-02-08 14:41:44.000000000 +1300
+++ squid-4.15/src/auth/basic/DB/basic_db_auth.8	2021-05-10 22:40:08.000000000 +1200
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "BASIC_DB_AUTH 8"
-.TH BASIC_DB_AUTH 8 "2021-02-08" "perl v5.32.0" "User Contributed Perl Documentation"
+.TH BASIC_DB_AUTH 8 "2021-05-10" "perl v5.32.0" "User Contributed Perl Documentation"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
diff -u -r -N squid-4.14/src/auth/basic/POP3/basic_pop3_auth.8 squid-4.15/src/auth/basic/POP3/basic_pop3_auth.8
--- squid-4.14/src/auth/basic/POP3/basic_pop3_auth.8	2021-02-08 14:41:45.000000000 +1300
+++ squid-4.15/src/auth/basic/POP3/basic_pop3_auth.8	2021-05-10 22:40:08.000000000 +1200
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "BASIC_POP3_AUTH 8"
-.TH BASIC_POP3_AUTH 8 "2021-02-08" "perl v5.32.0" "User Contributed Perl Documentation"
+.TH BASIC_POP3_AUTH 8 "2021-05-10" "perl v5.32.0" "User Contributed Perl Documentation"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
diff -u -r -N squid-4.14/src/cache_manager.cc squid-4.15/src/cache_manager.cc
--- squid-4.14/src/cache_manager.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/cache_manager.cc	2021-05-10 14:04:15.000000000 +1200
@@ -26,7 +26,9 @@
 #include "mgr/Forwarder.h"
 #include "mgr/FunAction.h"
 #include "mgr/QueryParams.h"
+#include "parser/Tokenizer.h"
 #include "protos.h"
+#include "sbuf/Stream.h"
 #include "sbuf/StringConvert.h"
 #include "SquidConfig.h"
 #include "SquidTime.h"
@@ -147,82 +149,87 @@
     return cmd->profile->creator->create(cmd);
 }
 
+static const CharacterSet &
+MgrFieldChars(const AnyP::ProtocolType &protocol)
+{
+    // Deprecated cache_object:// scheme used '@' to delimit passwords
+    if (protocol == AnyP::PROTO_CACHE_OBJECT) {
+        static const CharacterSet fieldChars = CharacterSet("cache-object-field", "@?#").complement();
+        return fieldChars;
+    }
+
+    static const CharacterSet actionChars = CharacterSet("mgr-field", "?#").complement();
+    return actionChars;
+}
+
 /**
- \ingroup CacheManagerInternal
  * define whether the URL is a cache-manager URL and parse the action
  * requested by the user. Checks via CacheManager::ActionProtection() that the
  * item is accessible by the user.
- \retval CacheManager::cachemgrStateData state object for the following handling
- \retval NULL if the action can't be found or can't be accessed by the user
+ *
+ * Syntax:
+ *
+ *  scheme "://" authority [ '/squid-internal-mgr' ] path-absolute [ '@' unreserved ] '?' query-string
+ *
+ * see RFC 3986 for definitions of scheme, authority, path-absolute, query-string
+ *
+ * \returns Mgr::Command object with action to perform and parameters it might use
  */
 Mgr::Command::Pointer
-CacheManager::ParseUrl(const char *url)
+CacheManager::ParseUrl(const AnyP::Uri &uri)
 {
-    int t;
-    LOCAL_ARRAY(char, host, MAX_URL);
-    LOCAL_ARRAY(char, request, MAX_URL);
-    LOCAL_ARRAY(char, password, MAX_URL);
-    LOCAL_ARRAY(char, params, MAX_URL);
-    host[0] = 0;
-    request[0] = 0;
-    password[0] = 0;
-    params[0] = 0;
-    int pos = -1;
-    int len = strlen(url);
-    Must(len > 0);
-    t = sscanf(url, "cache_object://%[^/]/%[^@?]%n@%[^?]?%s", host, request, &pos, password, params);
-    if (t < 3) {
-        t = sscanf(url, "cache_object://%[^/]/%[^?]%n?%s", host, request, &pos, params);
-    }
-    if (t < 1) {
-        t = sscanf(url, "http://%[^/]/squid-internal-mgr/%[^?]%n?%s", host, request, &pos, params);
-    }
-    if (t < 1) {
-        t = sscanf(url, "https://%[^/]/squid-internal-mgr/%[^?]%n?%s", host, request, &pos, params);
-    }
-    if (t < 2) {
-        if (strncmp("cache_object://",url,15)==0)
-            xstrncpy(request, "menu", MAX_URL);
-        else
-            xstrncpy(request, "index", MAX_URL);
-    }
+    Parser::Tokenizer tok(uri.path());
 
-#if _SQUID_OS2_
-    if (t == 2 && request[0] == '\0') {
-        /*
-         * emx's sscanf insists of returning 2 because it sets request
-         * to null
-         */
-        if (strncmp("cache_object://",url,15)==0)
-            xstrncpy(request, "menu", MAX_URL);
-        else
-            xstrncpy(request, "index", MAX_URL);
-    }
-#endif
+    static const SBuf internalMagicPrefix("/squid-internal-mgr/");
+    if (!tok.skip(internalMagicPrefix) && !tok.skip('/'))
+        throw TextException("invalid URL path", Here());
+
+    Mgr::Command::Pointer cmd = new Mgr::Command();
+    cmd->params.httpUri = SBufToString(uri.absolute());
 
-    debugs(16, 3, HERE << "MGR request: t=" << t << ", host='" << host << "', request='" << request << "', pos=" << pos <<
-           ", password='" << password << "', params='" << params << "'");
+    const auto &fieldChars = MgrFieldChars(uri.getScheme());
 
-    Mgr::ActionProfile::Pointer profile = findAction(request);
-    if (!profile) {
-        debugs(16, DBG_IMPORTANT, "CacheManager::ParseUrl: action '" << request << "' not found");
-        return NULL;
+    SBuf action;
+    if (!tok.prefix(action, fieldChars)) {
+        if (uri.getScheme() == AnyP::PROTO_CACHE_OBJECT) {
+            static const SBuf menuReport("menu");
+            action = menuReport;
+        } else {
+            static const SBuf indexReport("index");
+            action = indexReport;
+        }
     }
+    cmd->params.actionName = SBufToString(action);
+
+    const auto profile = findAction(action.c_str());
+    if (!profile)
+        throw TextException(ToSBuf("action '", action, "' not found"), Here());
 
     const char *prot = ActionProtection(profile);
-    if (!strcmp(prot, "disabled") || !strcmp(prot, "hidden")) {
-        debugs(16, DBG_IMPORTANT, "CacheManager::ParseUrl: action '" << request << "' is " << prot);
-        return NULL;
+    if (!strcmp(prot, "disabled") || !strcmp(prot, "hidden"))
+        throw TextException(ToSBuf("action '", action, "' is ", prot), Here());
+    cmd->profile = profile;
+
+    SBuf passwd;
+    if (uri.getScheme() == AnyP::PROTO_CACHE_OBJECT && tok.skip('@')) {
+        (void)tok.prefix(passwd, fieldChars);
+        cmd->params.password = SBufToString(passwd);
     }
 
-    Mgr::Command::Pointer cmd = new Mgr::Command;
-    if (!Mgr::QueryParams::Parse(params, cmd->params.queryParams))
-        return NULL;
-    cmd->profile = profile;
-    cmd->params.httpUri = url;
-    cmd->params.userName = String();
-    cmd->params.password = password;
-    cmd->params.actionName = request;
+    // TODO: fix when AnyP::Uri::parse() separates path?query#fragment
+    SBuf params;
+    if (tok.skip('?')) {
+        params = tok.remaining();
+        Mgr::QueryParams::Parse(tok, cmd->params.queryParams);
+    }
+
+    if (!tok.skip('#') && !tok.atEnd())
+        throw TextException("invalid characters in URL", Here());
+    // else ignore #fragment (if any)
+
+    debugs(16, 3, "MGR request: host=" << uri.host() << ", action=" << action <<
+           ", password=" << passwd << ", params=" << params);
+
     return cmd;
 }
 
@@ -305,11 +312,15 @@
 void
 CacheManager::Start(const Comm::ConnectionPointer &client, HttpRequest * request, StoreEntry * entry)
 {
-    debugs(16, 3, "CacheManager::Start: '" << entry->url() << "'" );
+    debugs(16, 3, "request-url= '" << request->url << "', entry-url='" << entry->url() << "'");
 
-    Mgr::Command::Pointer cmd = ParseUrl(entry->url());
-    if (!cmd) {
-        ErrorState *err = new ErrorState(ERR_INVALID_URL, Http::scNotFound, request);
+    Mgr::Command::Pointer cmd;
+    try {
+        cmd = ParseUrl(request->url);
+
+    } catch (...) {
+        debugs(16, 2, "request URL error: " << CurrentException);
+        const auto err = new ErrorState(ERR_INVALID_URL, Http::scNotFound, request);
         err->url = xstrdup(entry->url());
         errorAppendEntry(entry, err);
         entry->expires = squid_curtime;
@@ -473,4 +484,3 @@
     }
     return instance;
 }
-
diff -u -r -N squid-4.14/src/CacheManager.h squid-4.15/src/CacheManager.h
--- squid-4.14/src/CacheManager.h	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/CacheManager.h	2021-05-10 14:04:15.000000000 +1200
@@ -9,6 +9,7 @@
 #ifndef SQUID_CACHEMANAGER_H
 #define SQUID_CACHEMANAGER_H
 
+#include "anyp/forward.h"
 #include "comm/forward.h"
 #include "mgr/Action.h"
 #include "mgr/ActionProfile.h"
@@ -50,7 +51,7 @@
 protected:
     CacheManager() {} ///< use Instance() instead
 
-    Mgr::CommandPointer ParseUrl(const char *url);
+    Mgr::CommandPointer ParseUrl(const AnyP::Uri &);
     void ParseHeaders(const HttpRequest * request, Mgr::ActionParams &params);
     int CheckPassword(const Mgr::Command &cmd);
     char *PasswdGet(Mgr::ActionPasswordList *, const char *);
diff -u -r -N squid-4.14/src/cbdata.cc squid-4.15/src/cbdata.cc
--- squid-4.14/src/cbdata.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/cbdata.cc	2021-05-10 14:04:15.000000000 +1200
@@ -16,6 +16,7 @@
 #include "Store.h"
 
 #include <climits>
+#include <cstddef>
 
 #if USE_CBDATA_DEBUG
 #include <algorithm>
@@ -46,8 +47,6 @@
 
 #endif
 
-#define OFFSET_OF(TYPE, MEMBER) ((size_t) &(((TYPE) *)0)->(MEMBER))
-
 /**
  * Manage a set of registered callback data pointers.
  * One of the easiest ways to make Squid coredump is to issue a
@@ -91,6 +90,15 @@
     {}
     ~cbdata();
 
+    static cbdata *FromUserData(const void *p) {
+#if WITH_VALGRIND
+        return cbdata_htable.at(p);
+#else
+        const auto t = static_cast<const char *>(p) - offsetof(cbdata, data);
+        return reinterpret_cast<cbdata *>(const_cast<char *>(t));
+#endif
+    }
+
     int valid;
     int32_t locks;
     cbdata_type type;
@@ -116,25 +124,14 @@
 
 #if !WITH_VALGRIND
     size_t dataSize() const { return sizeof(data);}
-    static long MakeOffset();
-    static const long Offset;
 #endif
     /* MUST be the last per-instance member */
     void *data;
 };
 
-const long cbdata::Cookie((long)0xDEADBEEF);
-#if !WITH_VALGRIND
-const long cbdata::Offset(MakeOffset());
+static_assert(std::is_standard_layout<cbdata>::value, "the behavior of offsetof(cbdata) is defined");
 
-long
-cbdata::MakeOffset()
-{
-    cbdata *zero = (cbdata *)0L;
-    void **dataOffset = &zero->data;
-    return (long)dataOffset;
-}
-#endif
+const long cbdata::Cookie((long)0xDEADBEEF);
 
 static OBJH cbdataDump;
 #if USE_CBDATA_DEBUG
@@ -186,8 +183,7 @@
     snprintf(label, strlen(name) + 20, "cbdata %s (%d)", name, (int) type);
 
 #if !WITH_VALGRIND
-    assert((size_t)cbdata::Offset == (sizeof(cbdata) - ((cbdata *)NULL)->dataSize()));
-    size += cbdata::Offset;
+    size += offsetof(cbdata, data);
 #endif
 
     cbdata_index[type].pool = memPoolCreate(label, size);
@@ -297,12 +293,7 @@
 void *
 cbdataInternalFree(void *p, const char *file, int line)
 {
-    cbdata *c;
-#if WITH_VALGRIND
-    c = cbdata_htable.at(p);
-#else
-    c = (cbdata *) (((char *) p) - cbdata::Offset);
-#endif
+    auto *c = cbdata::FromUserData(p);
 #if USE_CBDATA_DEBUG
     debugs(45, 3, p << " " << file << ":" << line);
 #else
@@ -333,16 +324,10 @@
 cbdataInternalLock(const void *p)
 #endif
 {
-    cbdata *c;
-
     if (p == NULL)
         return;
 
-#if WITH_VALGRIND
-    c = cbdata_htable.at(p);
-#else
-    c = (cbdata *) (((char *) p) - cbdata::Offset);
-#endif
+    auto *c = cbdata::FromUserData(p);
 
 #if USE_CBDATA_DEBUG
     debugs(45, 3, p << "=" << (c ? c->locks + 1 : -1) << " " << file << ":" << line);
@@ -365,16 +350,10 @@
 cbdataInternalUnlock(const void *p)
 #endif
 {
-    cbdata *c;
-
     if (p == NULL)
         return;
 
-#if WITH_VALGRIND
-    c = cbdata_htable.at(p);
-#else
-    c = (cbdata *) (((char *) p) - cbdata::Offset);
-#endif
+    auto *c = cbdata::FromUserData(p);
 
 #if USE_CBDATA_DEBUG
     debugs(45, 3, p << "=" << (c ? c->locks - 1 : -1) << " " << file << ":" << line);
@@ -411,18 +390,12 @@
 int
 cbdataReferenceValid(const void *p)
 {
-    cbdata *c;
-
     if (p == NULL)
         return 1;       /* A NULL pointer cannot become invalid */
 
     debugs(45, 9, p);
 
-#if WITH_VALGRIND
-    c = cbdata_htable.at(p);
-#else
-    c = (cbdata *) (((char *) p) - cbdata::Offset);
-#endif
+    const auto c = cbdata::FromUserData(p);
 
     c->check(__LINE__);
 
@@ -502,7 +475,7 @@
 #if WITH_VALGRIND
             int obj_size = pool->objectSize();
 #else
-            int obj_size = pool->objectSize() - cbdata::Offset;
+            int obj_size = pool->objectSize() - offsetof(cbdata, data);
 #endif
             storeAppendPrintf(sentry, "%s\t%d\t%ld\t%ld\n", pool->objectType() + 7, obj_size, (long int)pool->getMeter().inuse.currentLevel(), (long int)obj_size * pool->getMeter().inuse.currentLevel());
         }
diff -u -r -N squid-4.14/src/clients/Client.cc squid-4.15/src/clients/Client.cc
--- squid-4.14/src/clients/Client.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/clients/Client.cc	2021-05-10 14:04:15.000000000 +1200
@@ -533,8 +533,11 @@
     maybePurgeOthers();
 
     // adaptation may overwrite old offset computed using the virgin response
-    const bool partial = theFinalReply->contentRange();
-    currentOffset = partial ? theFinalReply->contentRange()->spec.offset : 0;
+    currentOffset = 0;
+    if (const auto cr = theFinalReply->contentRange()) {
+        if (cr->spec.offset != HttpHdrRangeSpec::UnknownPosition)
+            currentOffset = cr->spec.offset;
+    }
 }
 
 /// whether to prevent caching of an otherwise cachable response
diff -u -r -N squid-4.14/src/client_side.cc squid-4.15/src/client_side.cc
--- squid-4.14/src/client_side.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/client_side.cc	2021-05-10 14:04:15.000000000 +1200
@@ -728,8 +728,8 @@
  * warning: assumes that HTTP headers for individual ranges at the
  *          time of the actuall assembly will be exactly the same as
  *          the headers when clientMRangeCLen() is called */
-int
-ClientHttpRequest::mRangeCLen()
+int64_t
+ClientHttpRequest::mRangeCLen() const
 {
     int64_t clen = 0;
     MemBuf mb;
diff -u -r -N squid-4.14/src/client_side_request.cc squid-4.15/src/client_side_request.cc
--- squid-4.14/src/client_side_request.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/client_side_request.cc	2021-05-10 14:04:15.000000000 +1200
@@ -1094,9 +1094,6 @@
              * iter up at this point.
              */
             node->readBuffer.offset = request->range->lowestOffset(0);
-            http->range_iter.pos = request->range->begin();
-            http->range_iter.end = request->range->end();
-            http->range_iter.valid = true;
         }
     }
 
@@ -1954,6 +1951,30 @@
 #include "client_side_request.cci"
 #endif
 
+// XXX: This should not be a _request_ method. Move range_iter elsewhere.
+int64_t
+ClientHttpRequest::prepPartialResponseGeneration()
+{
+    assert(request);
+    assert(request->range);
+
+    range_iter.pos = request->range->begin();
+    range_iter.end = request->range->end();
+    range_iter.debt_size = 0;
+    const auto multipart = request->range->specs.size() > 1;
+    if (multipart)
+        range_iter.boundary = rangeBoundaryStr();
+    range_iter.valid = true; // TODO: Remove.
+    range_iter.updateSpec(); // TODO: Refactor to initialize rather than update.
+
+    assert(range_iter.pos != range_iter.end);
+    const auto &firstRange = *range_iter.pos;
+    assert(firstRange);
+    out.offset = firstRange->offset;
+
+    return multipart ? mRangeCLen() : firstRange->length;
+}
+
 #if USE_ADAPTATION
 /// Initiate an asynchronous adaptation transaction which will call us back.
 void
diff -u -r -N squid-4.14/src/client_side_request.h squid-4.15/src/client_side_request.h
--- squid-4.14/src/client_side_request.h	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/client_side_request.h	2021-05-10 14:04:15.000000000 +1200
@@ -131,7 +131,7 @@
 
     dlink_node active;
     dlink_list client_stream;
-    int mRangeCLen();
+    int64_t mRangeCLen() const;
 
     ClientRequestContext *calloutContext;
     void doCallouts();
@@ -148,6 +148,11 @@
     /// neither the current request nor the parsed request URI are known
     void setErrorUri(const char *errorUri);
 
+    /// Prepares to satisfy a Range request with a generated HTTP 206 response.
+    /// Initializes range_iter state to allow raw range_iter access.
+    /// \returns Content-Length value for the future response; never negative
+    int64_t prepPartialResponseGeneration();
+
     /// Build an error reply. For use with the callouts.
     void calloutsError(const err_type error, const int errDetail);
 
diff -u -r -N squid-4.14/src/comm/TcpAcceptor.cc squid-4.15/src/comm/TcpAcceptor.cc
--- squid-4.14/src/comm/TcpAcceptor.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/comm/TcpAcceptor.cc	2021-05-10 14:04:15.000000000 +1200
@@ -374,7 +374,13 @@
     }
 
     Must(sock >= 0);
+
+    // Sync with Comm ASAP so that abandoned details can properly close().
+    // XXX : these are not all HTTP requests. use a note about type and ip:port details->
+    // so we end up with a uniform "(HTTP|FTP-data|HTTPS|...) remote-ip:remote-port"
+    fd_open(sock, FD_SOCKET, "HTTP Request");
     details->fd = sock;
+
     details->remote = *gai;
 
     // lookup the local-end details of this new connection
@@ -419,10 +425,6 @@
     }
 
     /* fdstat update */
-    // XXX : these are not all HTTP requests. use a note about type and ip:port details->
-    // so we end up with a uniform "(HTTP|FTP-data|HTTPS|...) remote-ip:remote-port"
-    fd_open(sock, FD_SOCKET, "HTTP Request");
-
     fdd_table[sock].close_file = NULL;
     fdd_table[sock].close_line = 0;
 
diff -u -r -N squid-4.14/src/comm.cc squid-4.15/src/comm.cc
--- squid-4.14/src/comm.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/comm.cc	2021-05-10 14:04:15.000000000 +1200
@@ -1585,6 +1585,7 @@
             debugs(5, 5, "checkTimeouts: FD " << fd << " auto write timeout");
             Comm::SetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0);
             COMMIO_FD_WRITECB(fd)->finish(Comm::COMM_ERROR, ETIMEDOUT);
+            continue;
         } else if (AlreadyTimedOut(F))
             continue;
 
diff -u -r -N squid-4.14/src/gopher.cc squid-4.15/src/gopher.cc
--- squid-4.14/src/gopher.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/gopher.cc	2021-05-10 14:04:15.000000000 +1200
@@ -740,7 +740,10 @@
 
     assert(buf == gopherState->replybuf);
 
-    if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
+    // XXX: Should update delayId, statCounter, etc. before bailing
+    if (!entry->isAccepting()) {
+        debugs(10, 3, "terminating due to bad " << *entry);
+        // TODO: Do not abuse connection for triggering cleanup.
         gopherState->serverConn->close();
         return;
     }
@@ -837,6 +840,13 @@
         statCounter.server.other.kbytes_out += size;
     }
 
+    if (!entry->isAccepting()) {
+        debugs(10, 3, "terminating due to bad " << *entry);
+        // TODO: Do not abuse connection for triggering cleanup.
+        gopherState->serverConn->close();
+        return;
+    }
+
     if (errflag) {
         ErrorState *err;
         err = new ErrorState(ERR_WRITE_ERROR, Http::scServiceUnavailable, gopherState->fwd->request);
diff -u -r -N squid-4.14/src/http/RegisteredHeaders.cc squid-4.15/src/http/RegisteredHeaders.cc
--- squid-4.14/src/http/RegisteredHeaders.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/http/RegisteredHeaders.cc	2021-05-10 14:04:15.000000000 +1200
@@ -37,7 +37,7 @@
 const HeaderTableRecord&
 HeaderLookupTable_t::lookup (const char *buf, const std::size_t len) const {
     const HeaderTableRecord *r = HttpHeaderHashTable::lookup(buf, len);
-    if (!r)
+    if (!r || r->id == Http::HdrType::OTHER)
         return BadHdr;
     return *r;
 }
diff -u -r -N squid-4.14/src/http/Stream.cc squid-4.15/src/http/Stream.cc
--- squid-4.14/src/http/Stream.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/http/Stream.cc	2021-05-10 14:04:15.000000000 +1200
@@ -163,12 +163,13 @@
             return start;
         }
 
-    } else if (reply && reply->contentRange()) {
+    } else if (const auto cr = reply ? reply->contentRange() : nullptr) {
         /* request does not have ranges, but reply does */
         /** \todo FIXME: should use range_iter_pos on reply, as soon as reply->content_range
          *        becomes HttpHdrRange rather than HttpHdrRangeSpec.
          */
-        return http->out.offset + reply->contentRange()->spec.offset;
+        if (cr->spec.offset != HttpHdrRangeSpec::UnknownPosition)
+            return http->out.offset + cr->spec.offset;
     }
 
     return http->out.offset;
@@ -232,6 +233,10 @@
 
             // did we get at least what we expected, based on range specs?
 
+            // this Content-Range does not tell us how many bytes to expect
+            if (bytesExpected == HttpHdrRangeSpec::UnknownPosition)
+                return STREAM_NONE;
+
             if (bytesSent == bytesExpected) // got everything
                 return STREAM_COMPLETE;
 
@@ -444,59 +449,27 @@
     } else {
         /* XXX: TODO: Review, this unconditional set may be wrong. */
         rep->sline.set(rep->sline.version, Http::scPartialContent);
-        // web server responded with a valid, but unexpected range.
-        // will (try-to) forward as-is.
-        //TODO: we should cope with multirange request/responses
-        // TODO: review, since rep->content_range is always nil here.
-        bool replyMatchRequest = contentRange != nullptr ?
-                                 request->range->contains(contentRange->spec) :
-                                 true;
+
+        // before range_iter accesses
+        const auto actual_clen = http->prepPartialResponseGeneration();
+
         const int spec_count = http->request->range->specs.size();
-        int64_t actual_clen = -1;
 
         debugs(33, 3, "range spec count: " << spec_count <<
                " virgin clen: " << rep->content_length);
         assert(spec_count > 0);
         /* append appropriate header(s) */
         if (spec_count == 1) {
-            if (!replyMatchRequest) {
-                hdr->putContRange(contentRange);
-                actual_clen = rep->content_length;
-                //http->range_iter.pos = rep->content_range->spec.begin();
-                (*http->range_iter.pos)->offset = contentRange->spec.offset;
-                (*http->range_iter.pos)->length = contentRange->spec.length;
-
-            } else {
-                HttpHdrRange::iterator pos = http->request->range->begin();
-                assert(*pos);
-                /* append Content-Range */
-
-                if (!contentRange) {
-                    /* No content range, so this was a full object we are
-                     * sending parts of.
-                     */
-                    httpHeaderAddContRange(hdr, **pos, rep->content_length);
-                }
-
-                /* set new Content-Length to the actual number of bytes
-                 * transmitted in the message-body */
-                actual_clen = (*pos)->length;
-            }
+            const auto singleSpec = *http->request->range->begin();
+            assert(singleSpec);
+            httpHeaderAddContRange(hdr, *singleSpec, rep->content_length);
         } else {
             /* multipart! */
-            /* generate boundary string */
-            http->range_iter.boundary = http->rangeBoundaryStr();
             /* delete old Content-Type, add ours */
             hdr->delById(Http::HdrType::CONTENT_TYPE);
             httpHeaderPutStrf(hdr, Http::HdrType::CONTENT_TYPE,
                               "multipart/byteranges; boundary=\"" SQUIDSTRINGPH "\"",
                               SQUIDSTRINGPRINT(http->range_iter.boundary));
-            /* Content-Length is not required in multipart responses
-             * but it is always nice to have one */
-            actual_clen = http->mRangeCLen();
-
-            /* http->out needs to start where we want data at */
-            http->out.offset = http->range_iter.currentSpec()->offset;
         }
 
         /* replace Content-Length header */
@@ -504,9 +477,6 @@
         hdr->delById(Http::HdrType::CONTENT_LENGTH);
         hdr->putInt64(Http::HdrType::CONTENT_LENGTH, actual_clen);
         debugs(33, 3, "actual content length: " << actual_clen);
-
-        /* And start the range iter off */
-        http->range_iter.updateSpec();
     }
 }
 
diff -u -r -N squid-4.14/src/http/url_rewriters/LFS/url_lfs_rewrite.8 squid-4.15/src/http/url_rewriters/LFS/url_lfs_rewrite.8
--- squid-4.14/src/http/url_rewriters/LFS/url_lfs_rewrite.8	2021-02-08 14:41:45.000000000 +1300
+++ squid-4.15/src/http/url_rewriters/LFS/url_lfs_rewrite.8	2021-05-10 22:40:09.000000000 +1200
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "URL_LFS_REWRITE 8"
-.TH URL_LFS_REWRITE 8 "2021-02-08" "perl v5.32.0" "User Contributed Perl Documentation"
+.TH URL_LFS_REWRITE 8 "2021-05-10" "perl v5.32.0" "User Contributed Perl Documentation"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
diff -u -r -N squid-4.14/src/http.cc squid-4.15/src/http.cc
--- squid-4.14/src/http.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/http.cc	2021-05-10 14:04:15.000000000 +1200
@@ -65,15 +65,6 @@
 #include "DelayPools.h"
 #endif
 
-#define SQUID_ENTER_THROWING_CODE() try {
-#define SQUID_EXIT_THROWING_CODE(status) \
-    status = true; \
-    } \
-    catch (const std::exception &e) { \
-    debugs (11, 1, "Exception error:" << e.what()); \
-    status = false; \
-    }
-
 CBDATA_CLASS_INIT(HttpStateData);
 
 static const char *const crlf = "\r\n";
@@ -1235,6 +1226,11 @@
         Must(!flags.headers_parsed);
     }
 
+    if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
+        abortTransaction("store entry aborted while we were waiting for processReply()");
+        return;
+    }
+
     if (!flags.headers_parsed) { // have not parsed headers yet?
         PROF_start(HttpStateData_processReplyHeader);
         processReplyHeader();
@@ -1366,26 +1362,25 @@
 bool
 HttpStateData::decodeAndWriteReplyBody()
 {
-    const char *data = NULL;
-    int len;
-    bool wasThereAnException = false;
     assert(flags.chunked);
     assert(httpChunkDecoder);
-    SQUID_ENTER_THROWING_CODE();
-    MemBuf decodedData;
-    decodedData.init();
-    httpChunkDecoder->setPayloadBuffer(&decodedData);
-    const bool doneParsing = httpChunkDecoder->parse(inBuf);
-    inBuf = httpChunkDecoder->remaining(); // sync buffers after parse
-    len = decodedData.contentSize();
-    data=decodedData.content();
-    addVirginReplyBody(data, len);
-    if (doneParsing) {
-        lastChunk = 1;
-        flags.do_next_read = false;
+    try {
+        MemBuf decodedData;
+        decodedData.init();
+        httpChunkDecoder->setPayloadBuffer(&decodedData);
+        const bool doneParsing = httpChunkDecoder->parse(inBuf);
+        inBuf = httpChunkDecoder->remaining(); // sync buffers after parse
+        addVirginReplyBody(decodedData.content(), decodedData.contentSize());
+        if (doneParsing) {
+            lastChunk = 1;
+            flags.do_next_read = false;
+        }
+        return true;
+    }
+    catch (...) {
+        debugs (11, 2, "de-chunking failure: " << CurrentException);
     }
-    SQUID_EXIT_THROWING_CODE(wasThereAnException);
-    return wasThereAnException;
+    return false;
 }
 
 /**
diff -u -r -N squid-4.14/src/HttpHdrContRange.cc squid-4.15/src/HttpHdrContRange.cc
--- squid-4.14/src/HttpHdrContRange.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/HttpHdrContRange.cc	2021-05-10 14:04:15.000000000 +1200
@@ -161,9 +161,13 @@
 
     ++p;
 
-    if (*p == '*')
+    if (*p == '*') {
+        if (!known_spec(range->spec.offset)) {
+            debugs(68, 2, "invalid (*/*) content-range-spec near: '" << str << "'");
+            return 0;
+        }
         range->elength = range_spec_unknown;
-    else if (!httpHeaderParseOffset(p, &range->elength))
+    } else if (!httpHeaderParseOffset(p, &range->elength))
         return 0;
     else if (range->elength <= 0) {
         /* Additional paranoidal check for BUG2155 - entity-length MUST be > 0 */
@@ -174,6 +178,12 @@
         return 0;
     }
 
+    // reject unsatisfied-range and such; we only use well-defined ranges today
+    if (!known_spec(range->spec.offset) || !known_spec(range->spec.length)) {
+        debugs(68, 2, "unwanted content-range-spec near: '" << str << "'");
+        return 0;
+    }
+
     debugs(68, 8, "parsed content-range field: " <<
            (long int) range->spec.offset << "-" <<
            (long int) range->spec.offset + range->spec.length - 1 << " / " <<
diff -u -r -N squid-4.14/src/HttpHdrRange.cc squid-4.15/src/HttpHdrRange.cc
--- squid-4.14/src/HttpHdrRange.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/HttpHdrRange.cc	2021-05-10 14:04:15.000000000 +1200
@@ -526,23 +526,6 @@
     return true;
 }
 
-bool
-HttpHdrRange::contains(const HttpHdrRangeSpec& r) const
-{
-    assert(r.length >= 0);
-    HttpHdrRangeSpec::HttpRange rrange(r.offset, r.offset + r.length);
-
-    for (const_iterator i = begin(); i != end(); ++i) {
-        HttpHdrRangeSpec::HttpRange irange((*i)->offset, (*i)->offset + (*i)->length);
-        HttpHdrRangeSpec::HttpRange intersection = rrange.intersection(irange);
-
-        if (intersection.start == irange.start && intersection.size() == irange.size())
-            return true;
-    }
-
-    return false;
-}
-
 const HttpHdrRangeSpec *
 HttpHdrRangeIter::currentSpec() const
 {
diff -u -r -N squid-4.14/src/HttpHeader.cc squid-4.15/src/HttpHeader.cc
--- squid-4.14/src/HttpHeader.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/HttpHeader.cc	2021-05-10 14:04:15.000000000 +1200
@@ -33,6 +33,7 @@
 #include "util.h"
 
 #include <algorithm>
+#include <array>
 
 /* XXX: the whole set of API managing the entries vector should be rethought
  *      after the parse4r-ng effort is complete.
@@ -72,7 +73,7 @@
 
 /* header accounting */
 // NP: keep in sync with enum http_hdr_owner_type
-static HttpHeaderStat HttpHeaderStats[] = {
+static std::array<HttpHeaderStat, hoEnd> HttpHeaderStats = {
     HttpHeaderStat(/*hoNone*/ "all", NULL),
 #if USE_HTCP
     HttpHeaderStat(/*hoHtcpReply*/ "HTCP reply", &ReplyHeadersMask),
@@ -80,11 +81,10 @@
     HttpHeaderStat(/*hoRequest*/ "request", &RequestHeadersMask),
     HttpHeaderStat(/*hoReply*/ "reply", &ReplyHeadersMask)
 #if USE_OPENSSL
-    /* hoErrorDetail */
+    , HttpHeaderStat(/*hoErrorDetail*/ "error detail templates", nullptr)
 #endif
     /* hoEnd */
 };
-static int HttpHeaderStatCount = countof(HttpHeaderStats);
 
 static int HeaderEntryParsedCount = 0;
 
@@ -127,8 +127,8 @@
             CBIT_SET(ReplyHeadersMask,h);
     }
 
-    /* header stats initialized by class constructor */
-    assert(HttpHeaderStatCount == hoReply + 1);
+    assert(HttpHeaderStats[0].label && "httpHeaderInitModule() called via main()");
+    assert(HttpHeaderStats[hoEnd-1].label && "HttpHeaderStats created with all elements");
 
     /* init dependent modules */
     httpHdrCcInitModule();
@@ -1607,6 +1607,9 @@
     assert(hs);
     assert(e);
 
+    if (!hs->owner_mask)
+        return; // these HttpHeaderStat objects were not meant to be dumped here
+
     dump_stat = hs;
     storeAppendPrintf(e, "\nHeader Stats: %s\n", hs->label);
     storeAppendPrintf(e, "\nField type distribution\n");
@@ -1632,7 +1635,6 @@
 void
 httpHeaderStoreReport(StoreEntry * e)
 {
-    int i;
     assert(e);
 
     HttpHeaderStats[0].parsedCount =
@@ -1644,9 +1646,8 @@
     HttpHeaderStats[0].busyDestroyedCount =
         HttpHeaderStats[hoRequest].busyDestroyedCount + HttpHeaderStats[hoReply].busyDestroyedCount;
 
-    for (i = 1; i < HttpHeaderStatCount; ++i) {
-        httpHeaderStatDump(HttpHeaderStats + i, e);
-    }
+    for (const auto &stats: HttpHeaderStats)
+        httpHeaderStatDump(&stats, e);
 
     /* field stats for all messages */
     storeAppendPrintf(e, "\nHttp Fields Stats (replies and requests)\n");
diff -u -r -N squid-4.14/src/HttpHeaderRange.h squid-4.15/src/HttpHeaderRange.h
--- squid-4.14/src/HttpHeaderRange.h	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/HttpHeaderRange.h	2021-05-10 14:04:15.000000000 +1200
@@ -18,8 +18,11 @@
 class HttpReply;
 class Packable;
 
-/* http byte-range-spec */
-
+// TODO: Refactor to disambiguate and provide message-specific APIs.
+/// either byte-range-spec (in a request Range header)
+/// or suffix-byte-range-spec (in a request Range header)
+/// or byte-range part of byte-range-resp (in a response Content-Range header)
+/// or "*" part of unsatisfied-range (in a response Content-Range header)
 class HttpHdrRangeSpec
 {
     MEMPROXY_CLASS(HttpHdrRangeSpec);
@@ -78,7 +81,6 @@
     int64_t firstOffset() const;
     int64_t lowestOffset(int64_t) const;
     bool offsetLimitExceeded(const int64_t limit) const;
-    bool contains(const HttpHdrRangeSpec& r) const;
     std::vector<HttpHdrRangeSpec *> specs;
 
 private:
@@ -100,9 +102,9 @@
     void updateSpec();
     int64_t debt() const;
     void debt(int64_t);
-    int64_t debt_size;      /* bytes left to send from the current spec */
+    int64_t debt_size = 0;  /* bytes left to send from the current spec */
     String boundary;        /* boundary for multipart responses */
-    bool valid;
+    bool valid = false;
 };
 
 #endif /* SQUID_HTTPHEADERRANGE_H */
diff -u -r -N squid-4.14/src/log/DB/log_db_daemon.8 squid-4.15/src/log/DB/log_db_daemon.8
--- squid-4.14/src/log/DB/log_db_daemon.8	2021-02-08 14:41:46.000000000 +1300
+++ squid-4.15/src/log/DB/log_db_daemon.8	2021-05-10 22:40:09.000000000 +1200
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "LOG_DB_DAEMON 8"
-.TH LOG_DB_DAEMON 8 "2021-02-08" "perl v5.32.0" "User Contributed Perl Documentation"
+.TH LOG_DB_DAEMON 8 "2021-05-10" "perl v5.32.0" "User Contributed Perl Documentation"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
diff -u -r -N squid-4.14/src/mgr/QueryParams.cc squid-4.15/src/mgr/QueryParams.cc
--- squid-4.14/src/mgr/QueryParams.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/mgr/QueryParams.cc	2021-05-10 14:04:15.000000000 +1200
@@ -14,6 +14,10 @@
 #include "mgr/IntParam.h"
 #include "mgr/QueryParams.h"
 #include "mgr/StringParam.h"
+#include "parser/Tokenizer.h"
+#include "sbuf/StringConvert.h"
+
+#include <limits>
 
 Mgr::QueryParam::Pointer
 Mgr::QueryParams::get(const String& name) const
@@ -65,61 +69,76 @@
     return iter;
 }
 
-bool
-Mgr::QueryParams::ParseParam(const String& paramStr, Param& param)
+/**
+ * Parses the value part of a "param=value" URL section.
+ * Value can be a comma-separated list of integers or an opaque string.
+ *
+ *   value  = *pchar | ( 1*DIGIT *( ',' 1*DIGIT ) )
+ *
+ * \note opaque string may be a list with a non-integer (e.g., "1,2,3,z")
+ */
+Mgr::QueryParam::Pointer
+ParseParamValue(const SBuf &rawValue)
+{
+    static const CharacterSet comma("comma", ",");
+
+    Parser::Tokenizer tok(rawValue);
+    std::vector<int> array;
+    int64_t intVal = 0;
+    while (tok.int64(intVal, 10, false)) {
+        Must(intVal >= std::numeric_limits<int>::min());
+        Must(intVal <= std::numeric_limits<int>::max());
+        array.emplace_back(intVal);
+        // integer list has comma between values.
+        // Require at least one potential DIGIT after the skipped ','
+        if (tok.remaining().length() > 1)
+            (void)tok.skipOne(comma);
+    }
+
+    if (tok.atEnd())
+        return new Mgr::IntParam(array);
+    else
+        return new Mgr::StringParam(SBufToString(rawValue));
+}
+
+/**
+ * Syntax:
+ *   query  = [ param *( '&' param ) ]
+ *   param  = name '=' value
+ *   name   = [a-zA-Z0-9]+
+ *   value  = *pchar | ( 1*DIGIT *( ',' 1*DIGIT ) )
+ */
+void
+Mgr::QueryParams::Parse(Parser::Tokenizer &tok, QueryParams &aParams)
 {
-    bool parsed = false;
-    regmatch_t pmatch[3];
-    regex_t intExpr;
-    regcomp(&intExpr, "^([a-z][a-z0-9_]*)=([0-9]+((,[0-9]+))*)$", REG_EXTENDED | REG_ICASE);
-    regex_t stringExpr;
-    regcomp(&stringExpr, "^([a-z][a-z0-9_]*)=([^&= ]+)$", REG_EXTENDED | REG_ICASE);
-    if (regexec(&intExpr, paramStr.termedBuf(), 3, pmatch, 0) == 0) {
-        param.first = paramStr.substr(pmatch[1].rm_so, pmatch[1].rm_eo);
-        std::vector<int> array;
-        int n = pmatch[2].rm_so;
-        for (int i = n; i < pmatch[2].rm_eo; ++i) {
-            if (paramStr[i] == ',') {
-                array.push_back(atoi(paramStr.substr(n, i).termedBuf()));
-                n = i + 1;
-            }
-        }
-        if (n < pmatch[2].rm_eo)
-            array.push_back(atoi(paramStr.substr(n, pmatch[2].rm_eo).termedBuf()));
-        param.second = new IntParam(array);
-        parsed = true;
-    } else if (regexec(&stringExpr, paramStr.termedBuf(), 3, pmatch, 0) == 0) {
-        param.first = paramStr.substr(pmatch[1].rm_so, pmatch[1].rm_eo);
-        param.second = new StringParam(paramStr.substr(pmatch[2].rm_so, pmatch[2].rm_eo));
-        parsed = true;
-    }
-    regfree(&stringExpr);
-    regfree(&intExpr);
-    return parsed;
-}
-
-bool
-Mgr::QueryParams::Parse(const String& aParamsStr, QueryParams& aParams)
-{
-    if (aParamsStr.size() != 0) {
-        Param param;
-        size_t n = 0;
-        size_t len = aParamsStr.size();
-        for (size_t i = n; i < len; ++i) {
-            if (aParamsStr[i] == '&') {
-                if (!ParseParam(aParamsStr.substr(n, i), param))
-                    return false;
-                aParams.params.push_back(param);
-                n = i + 1;
-            }
-        }
-        if (n < len) {
-            if (!ParseParam(aParamsStr.substr(n, len), param))
-                return false;
-            aParams.params.push_back(param);
-        }
+    static const CharacterSet nameChars = CharacterSet("param-name", "_") + CharacterSet::ALPHA + CharacterSet::DIGIT;
+    static const CharacterSet valueChars = CharacterSet("param-value", "&= #").complement();
+    static const CharacterSet delimChars("param-delim", "&");
+
+    while (!tok.atEnd()) {
+
+        // TODO: remove '#' processing when AnyP::Uri splits 'query#fragment' properly
+        // #fragment handled by caller. Do not throw.
+        if (tok.remaining()[0] == '#')
+            return;
+
+        if (tok.skipAll(delimChars))
+            continue;
+
+        SBuf nameStr;
+        if (!tok.prefix(nameStr, nameChars))
+            throw TextException("invalid query parameter name", Here());
+        if (!tok.skip('='))
+            throw TextException("missing parameter value", Here());
+
+        SBuf valueStr;
+        if (!tok.prefix(valueStr, valueChars))
+            throw TextException("missing or malformed parameter value", Here());
+
+        const auto name = SBufToString(nameStr);
+        const auto value = ParseParamValue(valueStr);
+        aParams.params.emplace_back(name, value);
     }
-    return true;
 }
 
 Mgr::QueryParam::Pointer
@@ -138,4 +157,3 @@
     }
     return NULL;
 }
-
diff -u -r -N squid-4.14/src/mgr/QueryParams.h squid-4.15/src/mgr/QueryParams.h
--- squid-4.14/src/mgr/QueryParams.h	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/mgr/QueryParams.h	2021-05-10 14:04:15.000000000 +1200
@@ -13,9 +13,11 @@
 
 #include "ipc/forward.h"
 #include "mgr/QueryParam.h"
+#include "parser/Tokenizer.h"
 #include "SquidString.h"
-#include <vector>
+
 #include <utility>
+#include <vector>
 
 namespace Mgr
 {
@@ -32,15 +34,13 @@
     void pack(Ipc::TypedMsgHdr& msg) const; ///< store params into msg
     void unpack(const Ipc::TypedMsgHdr& msg); ///< load params from msg
     /// parses the query string parameters
-    static bool Parse(const String& aParamsStr, QueryParams& aParams);
+    static void Parse(Parser::Tokenizer &, QueryParams &);
 
 private:
     /// find query parameter by name
     Params::const_iterator find(const String& name) const;
     /// creates a parameter of the specified type
     static QueryParam::Pointer CreateParam(QueryParam::Type aType);
-    /// parses string like "param=value"; returns true if success
-    static bool ParseParam(const String& paramStr, Param& param);
 
 private:
     Params params;
diff -u -r -N squid-4.14/src/security/cert_validators/fake/security_fake_certverify.8 squid-4.15/src/security/cert_validators/fake/security_fake_certverify.8
--- squid-4.14/src/security/cert_validators/fake/security_fake_certverify.8	2021-02-08 14:41:46.000000000 +1300
+++ squid-4.15/src/security/cert_validators/fake/security_fake_certverify.8	2021-05-10 22:40:09.000000000 +1200
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "SECURITY_FAKE_CERTVERIFY 8"
-.TH SECURITY_FAKE_CERTVERIFY 8 "2021-02-08" "perl v5.32.0" "User Contributed Perl Documentation"
+.TH SECURITY_FAKE_CERTVERIFY 8 "2021-05-10" "perl v5.32.0" "User Contributed Perl Documentation"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
diff -u -r -N squid-4.14/src/security/ServerOptions.cc squid-4.15/src/security/ServerOptions.cc
--- squid-4.14/src/security/ServerOptions.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/security/ServerOptions.cc	2021-05-10 14:04:15.000000000 +1200
@@ -24,6 +24,8 @@
 #endif
 #endif
 
+#include <limits>
+
 Security::ServerOptions &
 Security::ServerOptions::operator =(const Security::ServerOptions &old) {
     if (this != &old) {
diff -u -r -N squid-4.14/src/snmp_core.cc squid-4.15/src/snmp_core.cc
--- squid-4.14/src/snmp_core.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/snmp_core.cc	2021-05-10 14:04:15.000000000 +1200
@@ -947,7 +947,7 @@
     }
 
     // if we aborted before the lst octet was found, return false.
-    safe_free(name);
+    safe_free(*name);
     return false;
 }
 
diff -u -r -N squid-4.14/src/store/id_rewriters/file/storeid_file_rewrite.8 squid-4.15/src/store/id_rewriters/file/storeid_file_rewrite.8
--- squid-4.14/src/store/id_rewriters/file/storeid_file_rewrite.8	2021-02-08 14:41:44.000000000 +1300
+++ squid-4.15/src/store/id_rewriters/file/storeid_file_rewrite.8	2021-05-10 22:40:07.000000000 +1200
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "STOREID_FILE_REWRITE 8"
-.TH STOREID_FILE_REWRITE 8 "2021-02-08" "perl v5.32.0" "User Contributed Perl Documentation"
+.TH STOREID_FILE_REWRITE 8 "2021-05-10" "perl v5.32.0" "User Contributed Perl Documentation"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
diff -u -r -N squid-4.14/src/tests/stub_libmgr.cc squid-4.15/src/tests/stub_libmgr.cc
--- squid-4.14/src/tests/stub_libmgr.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/tests/stub_libmgr.cc	2021-05-10 14:04:15.000000000 +1200
@@ -174,11 +174,10 @@
 Mgr::QueryParam::Pointer Mgr::QueryParams::get(const String& name) const STUB_RETVAL(Mgr::QueryParam::Pointer(NULL))
 void Mgr::QueryParams::pack(Ipc::TypedMsgHdr& msg) const STUB
 void Mgr::QueryParams::unpack(const Ipc::TypedMsgHdr& msg) STUB
-bool Mgr::QueryParams::Parse(const String& aParamsStr, QueryParams& aParams) STUB_RETVAL(false)
+void Mgr::QueryParams::Parse(Parser::Tokenizer &, QueryParams &) STUB
 //private:
 //Params::const_iterator Mgr::QueryParams::find(const String& name) const STUB_RETVAL(new Mgr::Params::const_iterator(*this))
 Mgr::QueryParam::Pointer Mgr::QueryParams::CreateParam(QueryParam::Type aType) STUB_RETVAL(Mgr::QueryParam::Pointer(NULL))
-bool Mgr::QueryParams::ParseParam(const String& paramStr, Param& param) STUB_RETVAL(false)
 
 #include "mgr/Registration.h"
 //void Mgr::RegisterAction(char const * action, char const * desc, OBJH * handler, int pw_req_flag, int atomic);
diff -u -r -N squid-4.14/src/tests/testCacheManager.cc squid-4.15/src/tests/testCacheManager.cc
--- squid-4.14/src/tests/testCacheManager.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/tests/testCacheManager.cc	2021-05-10 14:04:15.000000000 +1200
@@ -7,6 +7,7 @@
  */
 
 #include "squid.h"
+#include "anyp/Uri.h"
 #include "CacheManager.h"
 #include "mgr/Action.h"
 #include "Store.h"
@@ -17,11 +18,19 @@
 
 CPPUNIT_TEST_SUITE_REGISTRATION( testCacheManager );
 
+/// Provides test code access to CacheManager internal symbols
+class CacheManagerInternals : public CacheManager
+{
+public:
+    void ParseUrl(const AnyP::Uri &u) { CacheManager::ParseUrl(u); }
+};
+
 /* init memory pools */
 
 void testCacheManager::setUp()
 {
     Mem::Init();
+    AnyP::UriScheme::Init();
 }
 
 /*
@@ -66,3 +75,146 @@
     CPPUNIT_ASSERT_EQUAL(1,(int)sentry->flags);
 }
 
+void
+testCacheManager::testParseUrl()
+{
+    auto *mgr = static_cast<CacheManagerInternals *>(CacheManager::GetInstance());
+    CPPUNIT_ASSERT(mgr != nullptr);
+
+    std::vector<AnyP::ProtocolType> validSchemes = {
+        AnyP::PROTO_CACHE_OBJECT,
+        AnyP::PROTO_HTTP,
+        AnyP::PROTO_HTTPS,
+        AnyP::PROTO_FTP
+    };
+
+    AnyP::Uri mgrUrl;
+    mgrUrl.host("localhost");
+    mgrUrl.port(3128);
+
+    const std::vector<const char *> magicPrefixes = {
+        "/",
+        "/squid-internal-mgr/"
+    };
+
+    const std::vector<const char *> validActions = {
+        "",
+        "menu"
+    };
+
+    const std::vector<const char *> invalidActions = {
+        "INVALID" // any unregistered name
+    };
+
+    const std::vector<const char *> validParams = {
+        "",
+        "?",
+        "?&",
+        "?&&&&&&&&&&&&",
+        "?foo=bar",
+        "?0123456789=bar",
+        "?foo=bar&",
+        "?foo=bar&&&&",
+        "?&foo=bar",
+        "?&&&&foo=bar",
+        "?&foo=bar&",
+        "?&&&&foo=bar&&&&",
+        "?foo=?_weird?~`:[]stuff&bar=okay&&&&&&",
+        "?intlist=1",
+        "?intlist=1,2,3,4,5",
+        "?string=1a",
+        "?string=1,2,3,4,z",
+        "?string=1,2,3,4,[0]",
+        "?intlist=1,2,3,4,5&string=1,2,3,4,y"
+    };
+
+    const std::vector<const char *> invalidParams = {
+        "?/",
+        "?foo",
+        "?/foo",
+        "?foo/",
+        "?foo=",
+        "?foo=&",
+        "?=foo",
+        "? foo=bar",
+        "? &",
+        "?& ",
+        "?=&",
+        "?&=",
+        "? &&&",
+        "?& &&",
+        "?&& &",
+        "?=&&&",
+        "?&=&&",
+        "?&&=&"
+    };
+
+    const std::vector<const char *> validFragments = {
+        "",
+        "#",
+        "##",
+        "#?a=b",
+        "#fragment"
+    };
+
+    for (const auto &scheme : validSchemes) {
+        mgrUrl.setScheme(scheme);
+
+        for (const auto *magic : magicPrefixes) {
+
+            // all schemes except cache_object require magic path prefix bytes
+            if (scheme != AnyP::PROTO_CACHE_OBJECT && strlen(magic) <= 2)
+                continue;
+
+            /* Check the parser accepts all the valid cases */
+
+            for (const auto *action : validActions) {
+                for (const auto *param : validParams) {
+                    for (const auto *frag : validFragments) {
+                        try {
+                            SBuf bits;
+                            bits.append(magic);
+                            bits.append(action);
+                            bits.append(param);
+                            bits.append(frag);
+                            mgrUrl.path(bits);
+
+                            (void)mgr->ParseUrl(mgrUrl);
+                        } catch (...) {
+                            std::cerr << std::endl
+                                      << "FAIL: " << mgrUrl
+                                      << Debug::Extra << "error: " << CurrentException << std::endl;
+                            CPPUNIT_FAIL("rejected a valid URL");
+                        }
+                    }
+                }
+            }
+
+            /* Check that invalid parameters are rejected */
+
+            for (const auto *action : validActions) {
+                for (const auto *param : invalidParams) {
+                    for (const auto *frag : validFragments) {
+                        try {
+                            SBuf bits;
+                            bits.append(magic);
+                            bits.append(action);
+                            bits.append(param);
+                            bits.append(frag);
+                            mgrUrl.path(bits);
+
+                            (void)mgr->ParseUrl(mgrUrl);
+
+                            std::cerr << std::endl
+                                      << "FAIL: " << mgrUrl
+                                      << Debug::Extra << "error: should be rejected due to '" << param << "'" << std::endl;
+                        } catch (const TextException &e) {
+                            continue; // success. caught bad input
+                        }
+                        CPPUNIT_FAIL("failed to reject an invalid URL");
+                    }
+                }
+            }
+        }
+    }
+}
diff -u -r -N squid-4.14/src/tests/testCacheManager.h squid-4.15/src/tests/testCacheManager.h
--- squid-4.14/src/tests/testCacheManager.h	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/tests/testCacheManager.h	2021-05-10 14:04:15.000000000 +1200
@@ -20,6 +20,7 @@
     CPPUNIT_TEST_SUITE( testCacheManager );
     CPPUNIT_TEST( testCreate );
     CPPUNIT_TEST( testRegister );
+    CPPUNIT_TEST( testParseUrl );
     CPPUNIT_TEST_SUITE_END();
 
 public:
@@ -28,6 +29,7 @@
 protected:
     void testCreate();
     void testRegister();
+    void testParseUrl();
 };
 
 #endif
diff -u -r -N squid-4.14/src/urn.cc squid-4.15/src/urn.cc
--- squid-4.14/src/urn.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/urn.cc	2021-05-10 14:04:15.000000000 +1200
@@ -244,6 +244,12 @@
         return;
     }
 
+    if (!e->isAccepting()) {
+        debugs(52, 3, "terminating due to bad " << *e);
+        delete urnState;
+        return;
+    }
+
     /* Update reqofs to point to where in the buffer we'd be */
     urnState->reqofs += result.length;
 
@@ -412,6 +418,7 @@
     }
 
     debugs(52, 3, "urnParseReply: Found " << i << " URLs");
+    xfree(buf);
     return list;
 }
 
diff -u -r -N squid-4.14/src/whois.cc squid-4.15/src/whois.cc
--- squid-4.14/src/whois.cc	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/src/whois.cc	2021-05-10 14:04:15.000000000 +1200
@@ -120,6 +120,16 @@
     debugs(75, 3, HERE << conn << " read " << aBufferLength << " bytes");
     debugs(75, 5, "{" << aBuffer << "}");
 
+    // TODO: Honor delay pools.
+
+    // XXX: Update statCounter before bailing
+    if (!entry->isAccepting()) {
+        debugs(52, 3, "terminating due to bad " << *entry);
+        // TODO: Do not abuse connection for triggering cleanup.
+        conn->close();
+        return;
+    }
+
     if (flag != Comm::OK) {
         debugs(50, 2, conn << ": read failure: " << xstrerr(xerrno));
 
diff -u -r -N squid-4.14/tools/helper-mux/helper-mux.8 squid-4.15/tools/helper-mux/helper-mux.8
--- squid-4.14/tools/helper-mux/helper-mux.8	2021-02-08 14:41:47.000000000 +1300
+++ squid-4.15/tools/helper-mux/helper-mux.8	2021-05-10 22:40:10.000000000 +1200
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "HELPER-MUX 8"
-.TH HELPER-MUX 8 "2021-02-08" "perl v5.32.0" "User Contributed Perl Documentation"
+.TH HELPER-MUX 8 "2021-05-10" "perl v5.32.0" "User Contributed Perl Documentation"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
diff -u -r -N squid-4.14/tools/squidclient/squidclient.1 squid-4.15/tools/squidclient/squidclient.1
--- squid-4.14/tools/squidclient/squidclient.1	2021-02-05 11:13:34.000000000 +1300
+++ squid-4.15/tools/squidclient/squidclient.1	2021-05-10 14:04:15.000000000 +1200
@@ -219,7 +219,7 @@
 .
 .if !'po4a'hide' .TP
 .if !'po4a'hide' .B "\-g count"
-Ping mode, perform\
+Ping mode, perform
 .I count
 iterations (default is to loop until interrupted).
 .
