Issue #11925 - Fix Etag NPE when using URLResource and improve Base Resource is alias warning (#11930)

* Issue #11925 - ee9 DefaultServlet and suffix url-patterns.
* Issue #11925 - Fix NPE in EtagUtils with URLResource
* Issue #11925 - Make error message "Base Resource should not be an alias" more useful.
* Set <reuseForks> to false for problematic tests.
This commit is contained in:
Joakim Erdfelt 2024-06-25 08:11:35 -05:00 committed by GitHub
parent 718c6fce51
commit f78f4422f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 124 additions and 10 deletions

View File

@ -66,13 +66,9 @@ public final class EtagUtils
*/
public static HttpField createWeakEtagField(Resource resource, String etagSuffix)
{
Path path = resource.getPath();
if (path == null)
return null;
String etagValue = EtagUtils.computeWeakEtag(path, etagSuffix);
if (etagValue != null)
return new PreEncodedHttpField(HttpHeader.ETAG, etagValue);
String etag = EtagUtils.computeWeakEtag(resource, etagSuffix);
if (etag != null)
return new PreEncodedHttpField(HttpHeader.ETAG, etag);
return null;
}
@ -213,6 +209,9 @@ public final class EtagUtils
*/
public static String rewriteWithSuffix(String etag, String newSuffix)
{
if (etag == null)
return null;
StringBuilder ret = new StringBuilder();
boolean weak = etag.startsWith("W/");
int start = 0;

View File

@ -217,4 +217,10 @@ public class MavenResource extends Resource
return null;
return _resource.resolve(subUriPath);
}
@Override
public String toString()
{
return "(Maven) " + _resource.toString();
}
}

View File

@ -277,4 +277,10 @@ public class SelectiveJarResource extends Resource
}
}
}
@Override
public String toString()
{
return "(Selective Jar/Maven) " + _delegate.toString();
}
}

View File

@ -715,7 +715,14 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias
if (!Resources.isReadable(baseResource))
throw new IllegalArgumentException("Base Resource is not valid: " + baseResource);
if (baseResource.isAlias())
LOG.warn("Base Resource should not be an alias");
{
URI realUri = baseResource.getRealURI();
if (realUri == null)
LOG.warn("Base Resource should not be an alias (100% of requests to context are subject to Security/Alias Checks): {}", baseResource);
else
LOG.warn("Base Resource should not be an alias (100% of requests to context are subject to Security/Alias Checks): {} points to {}",
baseResource, realUri.toASCIIString());
}
}
_availability.set(Availability.STARTING);

View File

@ -155,6 +155,6 @@ public class MemoryResource extends Resource
@Override
public String toString()
{
return getName();
return "(Memory) " + _uri.toASCIIString();
}
}

View File

@ -460,4 +460,13 @@ public abstract class Resource implements Iterable<Resource>
}
return false;
}
public String toString()
{
String str = getName();
URI uri = getURI();
if (uri != null)
str = getURI().toASCIIString();
return "(" + this.getClass().getSimpleName() + ") " + str;
}
}

View File

@ -308,7 +308,7 @@ public class URLResourceFactory implements ResourceFactory
@Override
public String toString()
{
return String.format("URLResource@%X(%s)", this.uri.hashCode(), this.uri.toASCIIString());
return "(URL) " + this.uri.toASCIIString();
}
private static class InputStreamReference extends AtomicReference<InputStream> implements Runnable

View File

@ -74,6 +74,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- Reuse Forks causes test failures -->
<reuseForks>false</reuseForks>
<argLine>@{argLine} ${jetty.surefire.argLine}
--add-modules org.eclipse.jetty.util.ajax
--add-reads org.eclipse.jetty.ee8.servlet=org.eclipse.jetty.logging</argLine>

View File

@ -72,6 +72,8 @@
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- Reuse Forks causes test failures -->
<reuseForks>false</reuseForks>
<argLine>@{argLine} ${jetty.surefire.argLine}
--add-modules org.eclipse.jetty.util.ajax
--add-reads org.eclipse.jetty.ee9.servlet=org.eclipse.jetty.logging</argLine>

View File

@ -13,6 +13,8 @@
package org.eclipse.jetty.ee9.servlet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URL;
@ -28,6 +30,8 @@ import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
@ -59,10 +63,12 @@ import org.eclipse.jetty.toolchain.test.MavenPaths;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.FileSystemPool;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.URLResourceFactory;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -2560,6 +2566,49 @@ public class DefaultServletTest
}
}
@Test
public void testGetPrecompressedSuffixMapping() throws Exception
{
Path docRoot = workDir.getEmptyPathDir().resolve("docroot");
FS.ensureDirExists(docRoot);
startServer((context) ->
{
ResourceFactory.registerResourceFactory("file", new URLResourceFactory());
Resource resource = ResourceFactory.of(context).newResource(docRoot);
assertThat("Expecting URLResource", resource.getClass().getName(), endsWith("URLResource"));
context.setBaseResource(resource);
ServletHolder defholder = context.addServlet(DefaultServlet.class, "*.js");
defholder.setInitParameter("cacheControl", "no-store");
defholder.setInitParameter("dirAllowed", "false");
defholder.setInitParameter("gzip", "false");
defholder.setInitParameter("precompressed", "gzip=.gz");
});
FS.ensureDirExists(docRoot.resolve("scripts"));
String scriptText = "This is a script";
Files.writeString(docRoot.resolve("scripts/script.js"), scriptText, UTF_8);
byte[] compressedBytes = compressGzip(scriptText);
Files.write(docRoot.resolve("scripts/script.js.gz"), compressedBytes);
String rawResponse = connector.getResponse("""
GET /context/scripts/script.js HTTP/1.1
Host: test
Accept-Encoding: gzip
Connection: close
""");
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertThat(response.getStatus(), is(HttpStatus.OK_200));
assertThat("Suffix url-pattern mapping not used", response.get(HttpHeader.CACHE_CONTROL), is("no-store"));
String responseDecompressed = decompressGzip(response.getContentBytes());
assertThat(responseDecompressed, is("This is a script"));
}
@Test
public void testHead() throws Exception
{
@ -2950,6 +2999,31 @@ public class DefaultServletTest
return ret;
}
private static byte[] compressGzip(String textToCompress) throws IOException
{
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzipOut = new GZIPOutputStream(baos);
ByteArrayInputStream input = new ByteArrayInputStream(textToCompress.getBytes(UTF_8)))
{
IO.copy(input, gzipOut);
gzipOut.flush();
gzipOut.finish();
return baos.toByteArray();
}
}
private static String decompressGzip(byte[] compressedContent) throws IOException
{
try (ByteArrayInputStream input = new ByteArrayInputStream(compressedContent);
GZIPInputStream gzipInput = new GZIPInputStream(input);
ByteArrayOutputStream output = new ByteArrayOutputStream())
{
IO.copy(gzipInput, output);
output.flush();
return output.toString(UTF_8);
}
}
public static class Scenarios extends ArrayList<Arguments>
{
public void addScenario(String description, String rawRequest, int expectedStatus)

View File

@ -66,8 +66,10 @@ import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.UrlEncoded;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.FileSystemPool;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@ -75,6 +77,7 @@ import org.slf4j.LoggerFactory;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -124,6 +127,12 @@ public class DispatcherTest
return _contextCollection;
}
@BeforeEach
public void ensureFileSystemPoolIsSane()
{
assertThat(FileSystemPool.INSTANCE.mounts(), empty());
}
@AfterEach
public void destroy() throws Exception
{