diff --git a/CVE-2021-28169.patch b/CVE-2021-28169.patch
new file mode 100644
index 0000000000000000000000000000000000000000..1c0fec9802ecb8c153be0ab7500b8c9bec108402
--- /dev/null
+++ b/CVE-2021-28169.patch
@@ -0,0 +1,528 @@
+From aec4092cc718b61998c1de221c9c728f377cd430 Mon Sep 17 00:00:00 2001
+From: Lachlan
+Date: Thu, 13 May 2021 01:13:30 +1000
+Subject: [PATCH] Fixes #6263 - Review URI encoding in ConcatServlet &
+WelcomeFilter.
+
+Review URI encoding in ConcatServlet & WelcomeFilter and improve testing.
+
+Signed-off-by: Lachlan Roberts
+Signed-off-by: Simone Bordet
+Co-authored-by: Simone Bordet
+---
+ .../eclipse/jetty/server/ResourceService.java | 4 +-
+ .../eclipse/jetty/servlets/ConcatServlet.java | 4 +-
+ .../eclipse/jetty/servlets/WelcomeFilter.java | 8 +-
+ .../jetty/servlets/ConcatServletTest.java | 83 ++++++----
+ .../jetty/servlets/WelcomeFilterTest.java | 143 ++++++++++++++++++
+ .../webapp/WebAppDefaultServletTest.java | 142 +++++++++++++++++
+ 6 files changed, 353 insertions(+), 31 deletions(-)
+ create mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java
+ create mode 100644 jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java
+
+diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java
+index 3d3f05c..5ce24b4 100644
+--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java
++++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java
+@@ -236,7 +236,7 @@ public class ResourceService
+ // Find the content
+ content=_contentFactory.getContent(pathInContext,response.getBufferSize());
+ if (LOG.isDebugEnabled())
+- LOG.info("content={}",content);
++ LOG.debug("content={}", content);
+
+ // Not found?
+ if (content==null || !content.getResource().exists())
+@@ -422,7 +422,7 @@ public class ResourceService
+ return;
+ }
+
+- RequestDispatcher dispatcher=context.getRequestDispatcher(welcome);
++ RequestDispatcher dispatcher = context.getRequestDispatcher(URIUtil.encodePath(welcome));
+ if (dispatcher!=null)
+ {
+ // Forward to the index
+diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java
+index a4b7df0..f1d8e57 100644
+--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java
++++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java
+@@ -62,6 +62,7 @@ import org.eclipse.jetty.util.URIUtil;
+ * appropriate. This means that when not in development mode, the servlet must be
+ * restarted before changed content will be served.
+ */
++@Deprecated
+ public class ConcatServlet extends HttpServlet
+ {
+ private boolean _development;
+@@ -126,7 +127,8 @@ public class ConcatServlet extends HttpServlet
+ }
+ }
+
+- RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(path);
++ // Use the original string and not the decoded path as the Dispatcher will decode again.
++ RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(part);
+ if (dispatcher != null)
+ dispatchers.add(dispatcher);
+ }
+diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java
+index e67a067..492a8ca 100644
+--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java
++++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java
+@@ -28,6 +28,8 @@ import javax.servlet.ServletRequest;
+ import javax.servlet.ServletResponse;
+ import javax.servlet.http.HttpServletRequest;
+
++import org.eclipse.jetty.util.URIUtil;
++
+ /* ------------------------------------------------------------ */
+ /** Welcome Filter
+ * This filter can be used to server an index file for a directory
+@@ -42,6 +44,7 @@ import javax.servlet.http.HttpServletRequest;
+ *
+ * Requests to "/some/directory" will be redirected to "/some/directory/".
+ */
++@Deprecated
+ public class WelcomeFilter implements Filter
+ {
+ private String welcome;
+@@ -63,7 +66,10 @@ public class WelcomeFilter implements Filter
+ {
+ String path=((HttpServletRequest)request).getServletPath();
+ if (welcome!=null && path.endsWith("/"))
+- request.getRequestDispatcher(path+welcome).forward(request,response);
++ {
++ String uriInContext = URIUtil.encodePath(URIUtil.addPaths(path, welcome));
++ request.getRequestDispatcher(uriInContext).forward(request, response);
++ }
+ else
+ chain.doFilter(request, response);
+ }
+diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java
+index 3fcb9af..b815b35 100644
+--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java
++++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java
+@@ -32,6 +32,7 @@ import java.nio.charset.StandardCharsets;
+ import java.nio.file.Files;
+ import java.nio.file.Path;
+
++import java.util.stream.Stream;
+ import javax.servlet.RequestDispatcher;
+ import javax.servlet.ServletException;
+ import javax.servlet.http.HttpServlet;
+@@ -48,7 +49,12 @@ import org.junit.jupiter.api.AfterEach;
+
+ import org.junit.jupiter.api.BeforeEach;
+ import org.junit.jupiter.api.Test;
++import org.junit.jupiter.params.ParameterizedTest;
++import org.junit.jupiter.params.provider.Arguments;
++import org.junit.jupiter.params.provider.MethodSource;
+
++import static org.hamcrest.MatcherAssert.assertThat;
++import static org.hamcrest.Matchers.startsWith;
+ public class ConcatServletTest
+ {
+ private Server server;
+@@ -114,7 +120,7 @@ public class ConcatServletTest
+ }
+
+ @Test
+- public void testWEBINFResourceIsNotServed() throws Exception
++ public void testDirectoryNotAccessible() throws Exception
+ {
+ File directoryFile = MavenTestingUtils.getTargetTestingDir();
+ Path directoryPath = directoryFile.toPath();
+@@ -136,9 +142,8 @@ public class ConcatServletTest
+ // Verify that I can get the file programmatically, as required by the spec.
+ assertNotNull(context.getServletContext().getResource("/WEB-INF/one.js"));
+
+- // Having a path segment and then ".." triggers a special case
+- // that the ConcatServlet must detect and avoid.
+- String uri = contextPath + concatPath + "?/trick/../WEB-INF/one.js";
++ // Make sure ConcatServlet cannot see file system files.
++ String uri = contextPath + concatPath + "?/trick/../../" + directoryFile.getName();
+ String request = "" +
+ "GET " + uri + " HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+@@ -146,35 +151,59 @@ public class ConcatServletTest
+ "\r\n";
+ String response = connector.getResponse(request);
+ assertTrue(response.startsWith("HTTP/1.1 404 "));
++ }
+
+- // Make sure ConcatServlet behaves well if it's case insensitive.
+- uri = contextPath + concatPath + "?/trick/../web-inf/one.js";
+- request = "" +
+- "GET " + uri + " HTTP/1.1\r\n" +
+- "Host: localhost\r\n" +
+- "Connection: close\r\n" +
+- "\r\n";
+- response = connector.getResponse(request);
+- assertTrue(response.startsWith("HTTP/1.1 404 "));
++ public static Stream webInfTestExamples()
++ {
++ return Stream.of(
++ // Cannot access WEB-INF.
++ Arguments.of("?/WEB-INF/", "HTTP/1.1 404 "),
++ Arguments.of("?/WEB-INF/one.js", "HTTP/1.1 404 "),
++
++ // Having a path segment and then ".." triggers a special case that the ConcatServlet must detect and avoid.
++ Arguments.of("?/trick/../WEB-INF/one.js", "HTTP/1.1 404 "),
++
++ // Make sure ConcatServlet behaves well if it's case insensitive.
++ Arguments.of("?/trick/../web-inf/one.js", "HTTP/1.1 404 "),
++
++ // Make sure ConcatServlet behaves well if encoded.
++ Arguments.of("?/trick/..%2FWEB-INF%2Fone.js", "HTTP/1.1 404 "),
++ Arguments.of("?/%2557EB-INF/one.js", "HTTP/1.1 500 "),
++ Arguments.of("?/js/%252e%252e/WEB-INF/one.js", "HTTP/1.1 500 ")
++ );
++ }
+
+- // Make sure ConcatServlet behaves well if encoded.
+- uri = contextPath + concatPath + "?/trick/..%2FWEB-INF%2Fone.js";
+- request = "" +
+- "GET " + uri + " HTTP/1.1\r\n" +
+- "Host: localhost\r\n" +
+- "Connection: close\r\n" +
+- "\r\n";
+- response = connector.getResponse(request);
+- assertTrue(response.startsWith("HTTP/1.1 404 "));
++ @ParameterizedTest
++ @MethodSource("webInfTestExamples")
++ public void testWEBINFResourceIsNotServed(String querystring, String expectedStatus) throws Exception
++ {
++ File directoryFile = MavenTestingUtils.getTargetTestingDir();
++ Path directoryPath = directoryFile.toPath();
++ Path hiddenDirectory = directoryPath.resolve("WEB-INF");
++ Files.createDirectories(hiddenDirectory);
++ Path hiddenResource = hiddenDirectory.resolve("one.js");
++ try (OutputStream output = Files.newOutputStream(hiddenResource))
++ {
++ output.write("function() {}".getBytes(StandardCharsets.UTF_8));
++ }
++
++ String contextPath = "";
++ WebAppContext context = new WebAppContext(server, directoryPath.toString(), contextPath);
++ server.setHandler(context);
++ String concatPath = "/concat";
++ context.addServlet(ConcatServlet.class, concatPath);
++ server.start();
+
+- // Make sure ConcatServlet cannot see file system files.
+- uri = contextPath + concatPath + "?/trick/../../" + directoryFile.getName();
+- request = "" +
++ // Verify that I can get the file programmatically, as required by the spec.
++ assertNotNull(context.getServletContext().getResource("/WEB-INF/one.js"));
++
++ String uri = contextPath + concatPath + querystring;
++ String request =
+ "GET " + uri + " HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n";
+- response = connector.getResponse(request);
+- assertTrue(response.startsWith("HTTP/1.1 404 "));
++ String response = connector.getResponse(request);
++ assertThat(response, startsWith(expectedStatus));
+ }
+ }
+diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java
+new file mode 100644
+index 0000000..65e6503
+--- /dev/null
++++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java
+@@ -0,0 +1,143 @@
++//
++// ========================================================================
++// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
++// ------------------------------------------------------------------------
++// All rights reserved. This program and the accompanying materials
++// are made available under the terms of the Eclipse Public License v1.0
++// and Apache License v2.0 which accompanies this distribution.
++//
++// The Eclipse Public License is available at
++// http://www.eclipse.org/legal/epl-v10.html
++//
++// The Apache License v2.0 is available at
++// http://www.opensource.org/licenses/apache2.0.php
++//
++// You may elect to redistribute this code under either of these licenses.
++// ========================================================================
++//
++
++package org.eclipse.jetty.servlets;
++
++import java.io.OutputStream;
++import java.nio.charset.StandardCharsets;
++import java.nio.file.Files;
++import java.nio.file.Path;
++import java.util.EnumSet;
++import java.util.stream.Stream;
++import javax.servlet.DispatcherType;
++
++import org.eclipse.jetty.server.LocalConnector;
++import org.eclipse.jetty.server.Server;
++import org.eclipse.jetty.servlet.FilterHolder;
++import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
++import org.eclipse.jetty.webapp.WebAppContext;
++import org.junit.jupiter.api.AfterEach;
++import org.junit.jupiter.api.BeforeEach;
++import org.junit.jupiter.params.ParameterizedTest;
++import org.junit.jupiter.params.provider.Arguments;
++import org.junit.jupiter.params.provider.MethodSource;
++
++import static org.hamcrest.MatcherAssert.assertThat;
++import static org.hamcrest.Matchers.containsString;
++import static org.junit.jupiter.api.Assertions.assertNotNull;
++
++public class WelcomeFilterTest
++{
++ private Server server;
++ private LocalConnector connector;
++
++ @BeforeEach
++ public void prepareServer() throws Exception
++ {
++ server = new Server();
++ connector = new LocalConnector(server);
++ server.addConnector(connector);
++
++ Path directoryPath = MavenTestingUtils.getTargetTestingDir().toPath();
++ Files.createDirectories(directoryPath);
++ Path welcomeResource = directoryPath.resolve("welcome.html");
++ try (OutputStream output = Files.newOutputStream(welcomeResource))
++ {
++ output.write("welcome page
".getBytes(StandardCharsets.UTF_8));
++ }
++
++ Path otherResource = directoryPath.resolve("other.html");
++ try (OutputStream output = Files.newOutputStream(otherResource))
++ {
++ output.write("other resource
".getBytes(StandardCharsets.UTF_8));
++ }
++
++ Path hiddenDirectory = directoryPath.resolve("WEB-INF");
++ Files.createDirectories(hiddenDirectory);
++ Path hiddenResource = hiddenDirectory.resolve("one.js");
++ try (OutputStream output = Files.newOutputStream(hiddenResource))
++ {
++ output.write("CONFIDENTIAL".getBytes(StandardCharsets.UTF_8));
++ }
++
++ Path hiddenWelcome = hiddenDirectory.resolve("index.html");
++ try (OutputStream output = Files.newOutputStream(hiddenWelcome))
++ {
++ output.write("CONFIDENTIAL".getBytes(StandardCharsets.UTF_8));
++ }
++
++ WebAppContext context = new WebAppContext(server, directoryPath.toString(), "/");
++ server.setHandler(context);
++ String concatPath = "/*";
++
++ FilterHolder filterHolder = new FilterHolder(new WelcomeFilter());
++ filterHolder.setInitParameter("welcome", "welcome.html");
++ context.addFilter(filterHolder, concatPath, EnumSet.of(DispatcherType.REQUEST));
++ server.start();
++
++ // Verify that I can get the file programmatically, as required by the spec.
++ assertNotNull(context.getServletContext().getResource("/WEB-INF/one.js"));
++ }
++
++ @AfterEach
++ public void destroy() throws Exception
++ {
++ if (server != null)
++ server.stop();
++ }
++
++ public static Stream argumentsStream()
++ {
++ return Stream.of(
++ // Normal requests for the directory are redirected to the welcome page.
++ Arguments.of("/", new String[]{"HTTP/1.1 200 ", "welcome page
"}),
++
++ // Try a normal resource (will bypass the filter).
++ Arguments.of("/other.html", new String[]{"HTTP/1.1 200 ", "other resource
"}),
++
++ // Cannot access files in WEB-INF.
++ Arguments.of("/WEB-INF/one.js", new String[]{"HTTP/1.1 404 "}),
++
++ // Cannot serve welcome from WEB-INF.
++ Arguments.of("/WEB-INF/", new String[]{"HTTP/1.1 404 "}),
++
++ // Try to trick the filter into serving a protected resource.
++ Arguments.of("/WEB-INF/one.js#/", new String[]{"HTTP/1.1 404 "}),
++ Arguments.of("/js/../WEB-INF/one.js#/", new String[]{"HTTP/1.1 404 "}),
++
++ // Test the URI is not double decoded in the dispatcher.
++ Arguments.of("/%2557EB-INF/one.js%23/", new String[]{"HTTP/1.1 404 "})
++ );
++ }
++
++ @ParameterizedTest
++ @MethodSource("argumentsStream")
++ public void testWelcomeFilter(String uri, String[] contains) throws Exception
++ {
++ String request =
++ "GET " + uri + " HTTP/1.1\r\n" +
++ "Host: localhost\r\n" +
++ "Connection: close\r\n" +
++ "\r\n";
++ String response = connector.getResponse(request);
++ for (String s : contains)
++ {
++ assertThat(response, containsString(s));
++ }
++ }
++}
+diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java
+new file mode 100644
+index 0000000..933bb7a
+--- /dev/null
++++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java
+@@ -0,0 +1,142 @@
++//
++// ========================================================================
++// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
++// ------------------------------------------------------------------------
++// All rights reserved. This program and the accompanying materials
++// are made available under the terms of the Eclipse Public License v1.0
++// and Apache License v2.0 which accompanies this distribution.
++//
++// The Eclipse Public License is available at
++// http://www.eclipse.org/legal/epl-v10.html
++//
++// The Apache License v2.0 is available at
++// http://www.opensource.org/licenses/apache2.0.php
++//
++// You may elect to redistribute this code under either of these licenses.
++// ========================================================================
++//
++
++package org.eclipse.jetty.webapp;
++
++import java.io.OutputStream;
++import java.nio.charset.StandardCharsets;
++import java.nio.file.Files;
++import java.nio.file.Path;
++import java.util.stream.Stream;
++
++import org.eclipse.jetty.server.LocalConnector;
++import org.eclipse.jetty.server.Server;
++import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
++import org.eclipse.jetty.util.IO;
++import org.junit.jupiter.api.AfterEach;
++import org.junit.jupiter.api.BeforeEach;
++import org.junit.jupiter.params.ParameterizedTest;
++import org.junit.jupiter.params.provider.Arguments;
++import org.junit.jupiter.params.provider.MethodSource;
++
++import static org.hamcrest.MatcherAssert.assertThat;
++import static org.hamcrest.Matchers.containsString;
++import static org.junit.jupiter.api.Assertions.assertNotNull;
++
++public class WebAppDefaultServletTest
++{
++ private Server server;
++ private LocalConnector connector;
++
++ @BeforeEach
++ public void prepareServer() throws Exception
++ {
++ server = new Server();
++ connector = new LocalConnector(server);
++ server.addConnector(connector);
++
++ Path directoryPath = MavenTestingUtils.getTargetTestingDir().toPath();
++ IO.delete(directoryPath.toFile());
++ Files.createDirectories(directoryPath);
++ Path welcomeResource = directoryPath.resolve("index.html");
++ try (OutputStream output = Files.newOutputStream(welcomeResource))
++ {
++ output.write("welcome page
".getBytes(StandardCharsets.UTF_8));
++ }
++
++ Path otherResource = directoryPath.resolve("other.html");
++ try (OutputStream output = Files.newOutputStream(otherResource))
++ {
++ output.write("other resource
".getBytes(StandardCharsets.UTF_8));
++ }
++
++ Path hiddenDirectory = directoryPath.resolve("WEB-INF");
++ Files.createDirectories(hiddenDirectory);
++ Path hiddenResource = hiddenDirectory.resolve("one.js");
++ try (OutputStream output = Files.newOutputStream(hiddenResource))
++ {
++ output.write("this is confidential".getBytes(StandardCharsets.UTF_8));
++ }
++
++ // Create directory to trick resource service.
++ Path hackPath = directoryPath.resolve("%57EB-INF/one.js#/");
++ Files.createDirectories(hackPath);
++ try (OutputStream output = Files.newOutputStream(hackPath.resolve("index.html")))
++ {
++ output.write("this content does not matter".getBytes(StandardCharsets.UTF_8));
++ }
++
++ Path standardHashDir = directoryPath.resolve("welcome#");
++ Files.createDirectories(standardHashDir);
++ try (OutputStream output = Files.newOutputStream(standardHashDir.resolve("index.html")))
++ {
++ output.write("standard hash dir welcome".getBytes(StandardCharsets.UTF_8));
++ }
++
++ WebAppContext context = new WebAppContext(server, directoryPath.toString(), "/");
++ server.setHandler(context);
++ server.start();
++
++ // Verify that I can get the file programmatically, as required by the spec.
++ assertNotNull(context.getServletContext().getResource("/WEB-INF/one.js"));
++ }
++
++ @AfterEach
++ public void destroy() throws Exception
++ {
++ if (server != null)
++ server.stop();
++ }
++
++ public static Stream argumentsStream()
++ {
++ return Stream.of(
++ Arguments.of("/WEB-INF/", new String[]{"HTTP/1.1 404 "}),
++ Arguments.of("/welcome%23/", new String[]{"HTTP/1.1 200 ", "standard hash dir welcome"}),
++
++ // Normal requests for the directory are redirected to the welcome page.
++ Arguments.of("/", new String[]{"HTTP/1.1 200 ", "welcome page
"}),
++
++ // We can be served other resources.
++ Arguments.of("/other.html", new String[]{"HTTP/1.1 200 ", "other resource
"}),
++
++ // The ContextHandler will filter these ones out as as WEB-INF is a protected target.
++ Arguments.of("/WEB-INF/one.js#/", new String[]{"HTTP/1.1 404 "}),
++ Arguments.of("/js/../WEB-INF/one.js#/", new String[]{"HTTP/1.1 404 "}),
++
++ // Test the URI is not double decoded by the dispatcher that serves the welcome file (we get index.html not one.js).
++ Arguments.of("/%2557EB-INF/one.js%23/", new String[]{"HTTP/1.1 200 ", "this content does not matter"})
++ );
++ }
++
++ @ParameterizedTest
++ @MethodSource("argumentsStream")
++ public void testResourceService(String uri, String[] contains) throws Exception
++ {
++ String request =
++ "GET " + uri + " HTTP/1.1\r\n" +
++ "Host: localhost\r\n" +
++ "Connection: close\r\n" +
++ "\r\n";
++ String response = connector.getResponse(request);
++ for (String s : contains)
++ {
++ assertThat(response, containsString(s));
++ }
++ }
++}
+--
+2.23.0
+
diff --git a/jetty.spec b/jetty.spec
index 9de9a8ca6980d951054fe43e7a772fc8afc8332a..8d7aa10d04e494d32a9fe1256be7d52804372447 100644
--- a/jetty.spec
+++ b/jetty.spec
@@ -12,7 +12,7 @@
%bcond_with jp_minimal
Name: jetty
Version: 9.4.15
-Release: 7
+Release: 8
Summary: Java Webserver and Servlet Container
License: ASL 2.0 or EPL-1.0 or EPL-2.0
URL: http://www.eclipse.org/jetty/
@@ -29,6 +29,8 @@ Patch4: CVE-2020-27223-pre-4.patch
Patch5: CVE-2020-27223.patch
Patch6: CVE-2021-28165-1.patch
Patch7: CVE-2021-28165-2.patch
+Patch8: CVE-2021-28169.patch
+
BuildRequires: maven-local mvn(javax.servlet:javax.servlet-api)
BuildRequires: mvn(org.apache.felix:maven-bundle-plugin)
BuildRequires: mvn(org.apache.maven.plugins:maven-shade-plugin)
@@ -787,6 +789,9 @@ exit 0
%license LICENSE NOTICE.txt LICENSE-MIT
%changelog
+* Wed Jun 23 2021 wangyue - 9.4.15-8
+- Fix CVE-2021-28169
+
* Wed Apr 21 2021 wangxiao - 9.4.15-7
- Fix CVE-2021-28165