diff --git a/SOURCES/squid-4.15-CVE-2021-28116.patch b/SOURCES/squid-4.15-CVE-2021-28116.patch
new file mode 100644
index 0000000..116a520
--- /dev/null
+++ b/SOURCES/squid-4.15-CVE-2021-28116.patch
@@ -0,0 +1,424 @@
+commit b003a0da7865caa25b5d1e70c79329b32409b02a (HEAD -> refs/heads/v4, refs/remotes/origin/v4)
+Author: Amos Jeffries <yadij@users.noreply.github.com>
+Date:   2021-09-24 21:53:11 +0000
+
+    WCCP: Validate packets better (#899)
+    
+    Update WCCP to support exception based error handling for
+    parsing and processing we are moving Squid to for protocol
+    handling.
+    
+    Update the main WCCPv2 parsing checks to throw meaningful
+    exceptions when detected.
+
+diff --git a/src/wccp2.cc b/src/wccp2.cc
+index ee592449c..6ef469e91 100644
+--- a/src/wccp2.cc
++++ b/src/wccp2.cc
+@@ -1108,6 +1108,59 @@ wccp2ConnectionClose(void)
+  * Functions for handling the requests.
+  */
+ 
++/// Checks that the given area section ends inside the given (whole) area.
++/// \param error the message to throw when the section does not fit
++static void
++CheckSectionLength(const void *sectionStart, const size_t sectionLength, const void *wholeStart, const size_t wholeSize, const char *error)
++{
++    assert(sectionStart);
++    assert(wholeStart);
++
++    const auto wholeEnd = static_cast<const char*>(wholeStart) + wholeSize;
++    assert(sectionStart >= wholeStart && "we never go backwards");
++    assert(sectionStart <= wholeEnd && "we never go beyond our whole (but zero-sized fields are OK)");
++    static_assert(sizeof(wccp2_i_see_you_t) <= PTRDIFF_MAX, "paranoid: no UB when subtracting in-whole pointers");
++    // subtraction safe due to the three assertions above
++    const auto remainderDiff = wholeEnd - static_cast<const char*>(sectionStart);
++
++    // casting safe due to the assertions above (and size_t definition)
++    assert(remainderDiff >= 0);
++    const auto remainderSize = static_cast<size_t>(remainderDiff);
++
++    if (sectionLength <= remainderSize)
++        return;
++
++    throw TextException(error, Here());
++}
++
++/// Checks that the area contains at least dataLength bytes after the header.
++/// The size of the field header itself is not included in dataLength.
++/// \returns the total field size -- the field header and field data combined
++template<class FieldHeader>
++static size_t
++CheckFieldDataLength(const FieldHeader *header, const size_t dataLength, const void *areaStart, const size_t areaSize, const char *error)
++{
++    assert(header);
++    const auto dataStart = reinterpret_cast<const char*>(header) + sizeof(header);
++    CheckSectionLength(dataStart, dataLength, areaStart, areaSize, error);
++    return sizeof(header) + dataLength; // no overflow after CheckSectionLength()
++}
++
++/// Positions the given field at a given start within a given packet area.
++/// The Field type determines the correct field size (used for bounds checking).
++/// \param field the field pointer the function should set
++/// \param areaStart the start of a packet (sub)structure containing the field
++/// \param areaSize the size of the packet (sub)structure starting at areaStart
++/// \param fieldStart the start of a field within the given area
++/// \param error the message to throw when the field does not fit the area
++template<class Field>
++static void
++SetField(Field *&field, const void *fieldStart, const void *areaStart, const size_t areaSize, const char *error)
++{
++    CheckSectionLength(fieldStart, sizeof(Field), areaStart, areaSize, error);
++    field = static_cast<Field*>(const_cast<void*>(fieldStart));
++}
++
+ /*
+  * Accept the UDP packet
+  */
+@@ -1124,8 +1177,6 @@ wccp2HandleUdp(int sock, void *)
+ 
+     /* These structs form the parts of the packet */
+ 
+-    struct wccp2_item_header_t *header = NULL;
+-
+     struct wccp2_security_none_t *security_info = NULL;
+ 
+     struct wccp2_service_info_t *service_info = NULL;
+@@ -1141,14 +1192,13 @@ wccp2HandleUdp(int sock, void *)
+     struct wccp2_cache_identity_info_t *cache_identity = NULL;
+ 
+     struct wccp2_capability_info_header_t *router_capability_header = NULL;
++    char *router_capability_data_start = nullptr;
+ 
+     struct wccp2_capability_element_t *router_capability_element;
+ 
+     struct sockaddr_in from;
+ 
+     struct in_addr cache_address;
+-    int len, found;
+-    short int data_length, offset;
+     uint32_t tmp;
+     char *ptr;
+     int num_caches;
+@@ -1161,20 +1211,18 @@ wccp2HandleUdp(int sock, void *)
+     Ip::Address from_tmp;
+     from_tmp.setIPv4();
+ 
+-    len = comm_udp_recvfrom(sock,
+-                            &wccp2_i_see_you,
+-                            WCCP_RESPONSE_SIZE,
+-                            0,
+-                            from_tmp);
++    const auto lenOrError = comm_udp_recvfrom(sock, &wccp2_i_see_you, WCCP_RESPONSE_SIZE, 0, from_tmp);
+ 
+-    if (len < 0)
++    if (lenOrError < 0)
+         return;
++    const auto len = static_cast<size_t>(lenOrError);
+ 
+-    if (ntohs(wccp2_i_see_you.version) != WCCP2_VERSION)
+-        return;
+-
+-    if (ntohl(wccp2_i_see_you.type) != WCCP2_I_SEE_YOU)
+-        return;
++    try {
++        // TODO: Remove wccp2_i_see_you.data and use a buffer to read messages.
++        const auto message_header_size = sizeof(wccp2_i_see_you) - sizeof(wccp2_i_see_you.data);
++        Must2(len >= message_header_size, "incomplete WCCP message header");
++        Must2(ntohs(wccp2_i_see_you.version) == WCCP2_VERSION, "WCCP version unsupported");
++        Must2(ntohl(wccp2_i_see_you.type) == WCCP2_I_SEE_YOU, "WCCP packet type unsupported");
+ 
+     /* FIXME INET6 : drop conversion boundary */
+     from_tmp.getSockAddr(from);
+@@ -1182,73 +1230,60 @@ wccp2HandleUdp(int sock, void *)
+     debugs(80, 3, "Incoming WCCPv2 I_SEE_YOU length " << ntohs(wccp2_i_see_you.length) << ".");
+ 
+     /* Record the total data length */
+-    data_length = ntohs(wccp2_i_see_you.length);
++    const auto data_length = ntohs(wccp2_i_see_you.length);
++    Must2(data_length <= len - message_header_size,
++          "malformed packet claiming it's bigger than received data");
+ 
+-    offset = 0;
+-
+-    if (data_length > len) {
+-        debugs(80, DBG_IMPORTANT, "ERROR: Malformed WCCPv2 packet claiming it's bigger than received data");
+-        return;
+-    }
++    size_t offset = 0;
+ 
+     /* Go through the data structure */
+-    while (data_length > offset) {
++    while (offset + sizeof(struct wccp2_item_header_t) <= data_length) {
+ 
+         char *data = wccp2_i_see_you.data;
+ 
+-        header = (struct wccp2_item_header_t *) &data[offset];
++        const auto itemHeader = reinterpret_cast<const wccp2_item_header_t*>(&data[offset]);
++        const auto itemSize = CheckFieldDataLength(itemHeader, ntohs(itemHeader->length),
++                              data, data_length, "truncated record");
++        // XXX: Check "The specified length must be a multiple of 4 octets"
++        // requirement to avoid unaligned memory reads after the first item.
+ 
+-        switch (ntohs(header->type)) {
++        switch (ntohs(itemHeader->type)) {
+ 
+         case WCCP2_SECURITY_INFO:
+-
+-            if (security_info != NULL) {
+-                debugs(80, DBG_IMPORTANT, "Duplicate security definition");
+-                return;
+-            }
+-
+-            security_info = (struct wccp2_security_none_t *) &wccp2_i_see_you.data[offset];
++            Must2(!security_info, "duplicate security definition");
++            SetField(security_info, itemHeader, itemHeader, itemSize,
++                     "security definition truncated");
+             break;
+ 
+         case WCCP2_SERVICE_INFO:
+-
+-            if (service_info != NULL) {
+-                debugs(80, DBG_IMPORTANT, "Duplicate service_info definition");
+-                return;
+-            }
+-
+-            service_info = (struct wccp2_service_info_t *) &wccp2_i_see_you.data[offset];
++            Must2(!service_info, "duplicate service_info definition");
++            SetField(service_info, itemHeader, itemHeader, itemSize,
++                     "service_info definition truncated");
+             break;
+ 
+         case WCCP2_ROUTER_ID_INFO:
+-
+-            if (router_identity_info != NULL) {
+-                debugs(80, DBG_IMPORTANT, "Duplicate router_identity_info definition");
+-                return;
+-            }
+-
+-            router_identity_info = (struct router_identity_info_t *) &wccp2_i_see_you.data[offset];
++            Must2(!router_identity_info, "duplicate router_identity_info definition");
++            SetField(router_identity_info, itemHeader, itemHeader, itemSize,
++                     "router_identity_info definition truncated");
+             break;
+ 
+         case WCCP2_RTR_VIEW_INFO:
+-
+-            if (router_view_header != NULL) {
+-                debugs(80, DBG_IMPORTANT, "Duplicate router_view definition");
+-                return;
+-            }
+-
+-            router_view_header = (struct router_view_t *) &wccp2_i_see_you.data[offset];
++            Must2(!router_view_header, "duplicate router_view definition");
++            SetField(router_view_header, itemHeader, itemHeader, itemSize,
++                     "router_view definition truncated");
+             break;
+ 
+-        case WCCP2_CAPABILITY_INFO:
+-
+-            if (router_capability_header != NULL) {
+-                debugs(80, DBG_IMPORTANT, "Duplicate router_capability definition");
+-                return;
+-            }
++        case WCCP2_CAPABILITY_INFO: {
++            Must2(!router_capability_header, "duplicate router_capability definition");
++            SetField(router_capability_header, itemHeader, itemHeader, itemSize,
++                     "router_capability definition truncated");
+ 
+-            router_capability_header = (struct wccp2_capability_info_header_t *) &wccp2_i_see_you.data[offset];
++            CheckFieldDataLength(router_capability_header, ntohs(router_capability_header->capability_info_length),
++                                 itemHeader, itemSize, "capability info truncated");
++            router_capability_data_start = reinterpret_cast<char*>(router_capability_header) +
++                                           sizeof(*router_capability_header);
+             break;
++        }
+ 
+         /* Nothing to do for the types below */
+ 
+@@ -1257,22 +1292,17 @@ wccp2HandleUdp(int sock, void *)
+             break;
+ 
+         default:
+-            debugs(80, DBG_IMPORTANT, "Unknown record type in WCCPv2 Packet (" << ntohs(header->type) << ").");
++            debugs(80, DBG_IMPORTANT, "Unknown record type in WCCPv2 Packet (" << ntohs(itemHeader->type) << ").");
+         }
+ 
+-        offset += sizeof(struct wccp2_item_header_t);
+-        offset += ntohs(header->length);
+-
+-        if (offset > data_length) {
+-            debugs(80, DBG_IMPORTANT, "Error: WCCPv2 packet tried to tell us there is data beyond the end of the packet");
+-            return;
+-        }
++        offset += itemSize;
++        assert(offset <= data_length && "CheckFieldDataLength(itemHeader...) established that");
+     }
+ 
+-    if ((security_info == NULL) || (service_info == NULL) || (router_identity_info == NULL) || (router_view_header == NULL)) {
+-        debugs(80, DBG_IMPORTANT, "Incomplete WCCPv2 Packet");
+-        return;
+-    }
++    Must2(security_info, "packet missing security definition");
++    Must2(service_info, "packet missing service_info definition");
++    Must2(router_identity_info, "packet missing router_identity_info definition");
++    Must2(router_view_header, "packet missing router_view definition");
+ 
+     debugs(80, 5, "Complete packet received");
+ 
+@@ -1308,10 +1338,7 @@ wccp2HandleUdp(int sock, void *)
+             break;
+     }
+ 
+-    if (router_list_ptr->next == NULL) {
+-        debugs(80, DBG_IMPORTANT, "WCCPv2 Packet received from unknown router");
+-        return;
+-    }
++    Must2(router_list_ptr->next, "packet received from unknown router");
+ 
+     /* Set the router id */
+     router_list_ptr->info->router_address = router_identity_info->router_id_element.router_address;
+@@ -1331,11 +1358,20 @@ wccp2HandleUdp(int sock, void *)
+         }
+     } else {
+ 
+-        char *end = ((char *) router_capability_header) + sizeof(*router_capability_header) + ntohs(router_capability_header->capability_info_length) - sizeof(struct wccp2_capability_info_header_t);
+-
+-        router_capability_element = (struct wccp2_capability_element_t *) (((char *) router_capability_header) + sizeof(*router_capability_header));
+-
+-        while ((char *) router_capability_element <= end) {
++        const auto router_capability_data_length = ntohs(router_capability_header->capability_info_length);
++        assert(router_capability_data_start);
++        const auto router_capability_data_end = router_capability_data_start +
++                                                router_capability_data_length;
++        for (auto router_capability_data_current = router_capability_data_start;
++                router_capability_data_current < router_capability_data_end;) {
++
++            SetField(router_capability_element, router_capability_data_current,
++                     router_capability_data_start, router_capability_data_length,
++                     "capability element header truncated");
++            const auto elementSize = CheckFieldDataLength(
++                                         router_capability_element, ntohs(router_capability_element->capability_length),
++                                         router_capability_data_start, router_capability_data_length,
++                                         "capability element truncated");
+ 
+             switch (ntohs(router_capability_element->capability_type)) {
+ 
+@@ -1377,7 +1413,7 @@ wccp2HandleUdp(int sock, void *)
+                 debugs(80, DBG_IMPORTANT, "Unknown capability type in WCCPv2 Packet (" << ntohs(router_capability_element->capability_type) << ").");
+             }
+ 
+-            router_capability_element = (struct wccp2_capability_element_t *) (((char *) router_capability_element) + sizeof(struct wccp2_item_header_t) + ntohs(router_capability_element->capability_length));
++            router_capability_data_current += elementSize;
+         }
+     }
+ 
+@@ -1396,23 +1432,34 @@ wccp2HandleUdp(int sock, void *)
+     num_caches = 0;
+ 
+     /* Check to see if we're the master cache and update the cache list */
+-    found = 0;
++    bool found = false;
+     service_list_ptr->lowest_ip = 1;
+     cache_list_ptr = &router_list_ptr->cache_list_head;
+ 
+     /* to find the list of caches, we start at the end of the router view header */
+ 
+     ptr = (char *) (router_view_header) + sizeof(struct router_view_t);
++    const auto router_view_size = sizeof(struct router_view_t) +
++                                  ntohs(router_view_header->header.length);
+ 
+     /* Then we read the number of routers */
+-    memcpy(&tmp, ptr, sizeof(tmp));
++    const uint32_t *routerCountRaw = nullptr;
++    SetField(routerCountRaw, ptr, router_view_header, router_view_size,
++             "malformed packet (truncated router view info w/o number of routers)");
+ 
+     /* skip the number plus all the ip's */
+-
+-    ptr += sizeof(tmp) + (ntohl(tmp) * sizeof(struct in_addr));
++    ptr += sizeof(*routerCountRaw);
++    const auto ipCount = ntohl(*routerCountRaw);
++    const auto ipsSize = ipCount * sizeof(struct in_addr); // we check for unsigned overflow below
++    Must2(ipsSize / sizeof(struct in_addr) != ipCount, "huge IP address count");
++    CheckSectionLength(ptr, ipsSize, router_view_header, router_view_size, "invalid IP address count");
++    ptr += ipsSize;
+ 
+     /* Then read the number of caches */
+-    memcpy(&tmp, ptr, sizeof(tmp));
++    const uint32_t *cacheCountRaw = nullptr;
++    SetField(cacheCountRaw, ptr, router_view_header, router_view_size,
++             "malformed packet (truncated router view info w/o cache count)");
++    memcpy(&tmp, cacheCountRaw, sizeof(tmp)); // TODO: Replace tmp with cacheCount
+     ptr += sizeof(tmp);
+ 
+     if (ntohl(tmp) != 0) {
+@@ -1426,7 +1473,8 @@ wccp2HandleUdp(int sock, void *)
+ 
+             case WCCP2_ASSIGNMENT_METHOD_HASH:
+ 
+-                cache_identity = (struct wccp2_cache_identity_info_t *) ptr;
++                SetField(cache_identity, ptr, router_view_header, router_view_size,
++                         "malformed packet (truncated router view info cache w/o assignment hash)");
+ 
+                 ptr += sizeof(struct wccp2_cache_identity_info_t);
+ 
+@@ -1437,13 +1485,15 @@ wccp2HandleUdp(int sock, void *)
+ 
+             case WCCP2_ASSIGNMENT_METHOD_MASK:
+ 
+-                cache_mask_info = (struct cache_mask_info_t *) ptr;
++                SetField(cache_mask_info, ptr, router_view_header, router_view_size,
++                         "malformed packet (truncated router view info cache w/o assignment mask)");
+ 
+                 /* The mask assignment has an undocumented variable length entry here */
+ 
+                 if (ntohl(cache_mask_info->num1) == 3) {
+ 
+-                    cache_mask_identity = (struct wccp2_cache_mask_identity_info_t *) ptr;
++                    SetField(cache_mask_identity, ptr, router_view_header, router_view_size,
++                             "malformed packet (truncated router view info cache w/o assignment mask identity)");
+ 
+                     ptr += sizeof(struct wccp2_cache_mask_identity_info_t);
+ 
+@@ -1474,10 +1524,7 @@ wccp2HandleUdp(int sock, void *)
+             debugs (80, 5,  "checking cache list: (" << std::hex << cache_address.s_addr << ":" <<  router_list_ptr->local_ip.s_addr << ")");
+ 
+             /* Check to see if it's the master, or us */
+-
+-            if (cache_address.s_addr == router_list_ptr->local_ip.s_addr) {
+-                found = 1;
+-            }
++            found = found || (cache_address.s_addr == router_list_ptr->local_ip.s_addr);
+ 
+             if (cache_address.s_addr < router_list_ptr->local_ip.s_addr) {
+                 service_list_ptr->lowest_ip = 0;
+@@ -1494,7 +1541,7 @@ wccp2HandleUdp(int sock, void *)
+         cache_list_ptr->next = NULL;
+ 
+         service_list_ptr->lowest_ip = 1;
+-        found = 1;
++        found = true;
+         num_caches = 1;
+     }
+ 
+@@ -1502,7 +1549,7 @@ wccp2HandleUdp(int sock, void *)
+ 
+     router_list_ptr->num_caches = htonl(num_caches);
+ 
+-    if ((found == 1) && (service_list_ptr->lowest_ip == 1)) {
++    if (found && (service_list_ptr->lowest_ip == 1)) {
+         if (ntohl(router_view_header->change_number) != router_list_ptr->member_change) {
+             debugs(80, 4, "Change detected - queueing up new assignment");
+             router_list_ptr->member_change = ntohl(router_view_header->change_number);
+@@ -1515,6 +1562,10 @@ wccp2HandleUdp(int sock, void *)
+         eventDelete(wccp2AssignBuckets, NULL);
+         debugs(80, 5, "I am not the lowest ip cache - not assigning buckets");
+     }
++
++    } catch (...) {
++        debugs(80, DBG_IMPORTANT, "ERROR: Ignoring WCCPv2 message: " << CurrentException);
++    }
+ }
+ 
+ static void
diff --git a/SOURCES/squid-4.15-ftp-filename-extraction.patch b/SOURCES/squid-4.15-ftp-filename-extraction.patch
new file mode 100644
index 0000000..cf1aeb3
--- /dev/null
+++ b/SOURCES/squid-4.15-ftp-filename-extraction.patch
@@ -0,0 +1,32 @@
+diff --git a/src/clients/FtpGateway.cc b/src/clients/FtpGateway.cc
+index da9867f..e992638 100644
+--- a/src/clients/FtpGateway.cc
++++ b/src/clients/FtpGateway.cc
+@@ -1084,16 +1084,17 @@ Ftp::Gateway::checkAuth(const HttpHeader * req_hdr)
+ void
+ Ftp::Gateway::checkUrlpath()
+ {
+-    static SBuf str_type_eq("type=");
+-    auto t = request->url.path().rfind(';');
+-
+-    if (t != SBuf::npos) {
+-        auto filenameEnd = t-1;
+-        if (request->url.path().substr(++t).cmp(str_type_eq, str_type_eq.length()) == 0) {
+-            t += str_type_eq.length();
+-            typecode = (char)xtoupper(request->url.path()[t]);
+-            request->url.path(request->url.path().substr(0,filenameEnd));
+-        }
++    // If typecode was specified, extract it and leave just the filename in
++    // url.path. Tolerate trailing garbage or missing typecode value. Roughly:
++    // [filename] ;type=[typecode char] [trailing garbage]
++    static const SBuf middle(";type=");
++    const auto typeSpecStart = request->url.path().find(middle);
++    if (typeSpecStart != SBuf::npos) {
++        const auto fullPath = request->url.path();
++        const auto typecodePos = typeSpecStart + middle.length();
++        typecode = (typecodePos < fullPath.length()) ?
++            static_cast<char>(xtoupper(fullPath[typecodePos])) : '\0';
++        request->url.path(fullPath.substr(0, typeSpecStart));
+     }
+ 
+     int l = request->url.path().length();
diff --git a/SPECS/squid.spec b/SPECS/squid.spec
index 20aa8ac..3252413 100644
--- a/SPECS/squid.spec
+++ b/SPECS/squid.spec
@@ -2,7 +2,7 @@
 
 Name:     squid
 Version:  4.15
-Release:  1%{?dist}
+Release:  3%{?dist}
 Summary:  The Squid proxy caching server
 Epoch:    7
 # See CREDITS for breakdown of non GPLv2+ code
@@ -34,8 +34,12 @@ Patch205: squid-4.11-large-acl.patch
 # https://bugzilla.redhat.com/show_bug.cgi?id=980511
 Patch206: squid-4.11-active-ftp.patch
 Patch208: squid-4.11-convert-ipv4.patch
+# https://bugzilla.redhat.com/show_bug.cgi?id=2006121
+Patch209: squid-4.15-ftp-filename-extraction.patch
 
 # Security fixes
+# https://bugzilla.redhat.com/show_bug.cgi?id=1941506
+Patch300: squid-4.15-CVE-2021-28116.patch
 
 Requires: bash >= 2.0
 Requires(pre): shadow-utils
@@ -95,8 +99,10 @@ lookup program (dnsserver), a program for retrieving FTP data
 %patch205 -p1 -b .large_acl
 %patch206 -p1 -b .active-ftp
 %patch208 -p1 -b .convert-ipv4
+%patch209 -p1 -b .ftp-fn-extraction
 
 # Security patches
+%patch300 -p1 -b .CVE-2021-28116
 
 # https://bugzilla.redhat.com/show_bug.cgi?id=1679526
 # Patch in the vendor documentation and used different location for documentation
@@ -313,6 +319,14 @@ fi
 
 
 %changelog
+* Wed Feb 09 2022 Luboš Uhliarik <luhliari@redhat.com> - 7:4.15-3
+- Resolves: #1941506 - CVE-2021-28116 squid:4/squid: out-of-bounds read in WCCP
+  protocol data may lead to information disclosure
+
+* Tue Jan 25 2022 Luboš Uhliarik <luhliari@redhat.com> - 7:4.15-2
+- Resolves: #2006121 - SQUID shortens FTP Link wrong that contains a semi-colon
+  and as a result is not able to download zip file.CODE 404 TO CLIENT)
+
 * Fri Jun 18 2021 Luboš Uhliarik <luhliari@redhat.com> - 7:4.15-1
 - new version 4.15
 - Resolves: #1964384 - squid:4 rebase to 4.15