Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-SignInWithEthereum

This commit is contained in:
Lachlan Roberts 2024-07-25 10:46:55 +10:00
commit 9581ef15af
No known key found for this signature in database
GPG Key ID: 5663FB7A8FF7E348
30 changed files with 1194 additions and 298 deletions

View File

@ -212,6 +212,7 @@ updates:
versions: [ ">=12" ]
- dependency-name: "com.hazelcast:*"
- dependency-name: "org.apache.directory*"
- dependency-name: "org.apache.maven.plugin-tools:*"
- dependency-name: "org.apache.felix:*"
versions: [ ">=4" ]
- dependency-name: "org.jboss.weld.servlet:*"

6
Jenkinsfile vendored
View File

@ -126,7 +126,7 @@ def mavenBuild(jdk, cmdline, mvnName) {
}
}
runLaunchable ("verify")
runLaunchable ("record build --name $BRANCH_NAME")
runLaunchable ("record build --name jetty-12.0.x")
sh "mvn $extraArgs -DsettingsPath=$GLOBAL_MVN_SETTINGS -Dmaven.repo.uri=http://nexus-service.nexus.svc.cluster.local:8081/repository/maven-public/ -ntp -s $GLOBAL_MVN_SETTINGS -Dmaven.repo.local=.repository -Pci -V -B -e -U $cmdline"
if(saveHome()) {
archiveArtifacts artifacts: ".repository/org/eclipse/jetty/jetty-home/**/jetty-home-*", allowEmptyArchive: true, onlyIfSuccessful: false
@ -136,9 +136,9 @@ def mavenBuild(jdk, cmdline, mvnName) {
}
finally
{
junit testResults: '**/target/surefire-reports/**/*.xml,**/target/invoker-reports/TEST*.xml', allowEmptyResults: true
junit testDataPublishers: [[$class: 'JUnitFlakyTestDataPublisher']], testResults: '**/target/surefire-reports/**/*.xml,**/target/invoker-reports/TEST*.xml', allowEmptyResults: true
echo "Launchable record tests"
runLaunchable ("record tests --build $BRANCH_NAME maven '**/target/surefire-reports/**/*.xml' '**/target/invoker-reports/TEST*.xml'")
runLaunchable ("record tests --build jetty-12.0.x maven '**/target/surefire-reports/**/*.xml' '**/target/invoker-reports/TEST*.xml'")
}
}
}

View File

@ -20,9 +20,9 @@ The following checklist is used to handle security issues:
- [ ] If the vulnerability cannot be confirmed then close the security advisory, else continue.
- [ ] Generate a CVE score and add it to the advisory description.
- [ ] Identify a CWE Definition and add it to the advisory description.
- [ ] Identify vulnerable version(s), including current and past versions that are affected (e.g. 9.4.0 through 9.4.35, and 10.0.0.alpha1 through 10.0.0.beta3…​etc.)
- [ ] Identify vulnerable version(s), including current and past versions that are affected (e.g. 9.4.0 through 9.4.35, and 10.0.0.alpha1 through 10.0.0.beta3 etc.)
- [ ] Identify and document workaround(s), if applicable, in the comments of the security advisory.
- [ ] Open an [Gitlab@Eclipse EMO CVE issue](https://gitlab.eclipse.org/eclipsefdn/emo-team/emo/-/issues/new?issuable_template=cve) to have a CVE allocated.
- [ ] Open an [Gitlab@Eclipse CVE Assignment](https://gitlab.eclipse.org/security/cve-assignement/-/issues/new) to have a CVE allocated.
The issue should be opened under the "Eclipse Foundation" > "EMO Team" > "EMO" section as a "cve" description, with the "This issue is confidential" checkbox checked.
Follow the template for what details are necessary to file for a CVE.
- [ ] Once the CVE is allocated update the Security Advisory with the number

View File

@ -18,7 +18,7 @@
<maven.javadoc.plugin.version>3.4.0</maven.javadoc.plugin.version>
<maven.javadoc.skip>true</maven.javadoc.skip>
<maven.remote-resources.plugin.version>3.2.0</maven.remote-resources.plugin.version>
<maven.surefire.plugin.version>3.2.5</maven.surefire.plugin.version>
<maven.surefire.plugin.version>3.3.1</maven.surefire.plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<skipTests>true</skipTests>
</properties>
@ -41,7 +41,7 @@
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>2.41.1</version>
<version>2.43.0</version>
<configuration>
<pom>
<includes>

View File

@ -75,7 +75,7 @@
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>h2spec-maven-plugin</artifactId>
<version>1.0.10</version>
<version>1.0.12</version>
<configuration>
<mainClass>org.eclipse.jetty.http2.tests.H2SpecServer</mainClass>
<skip>${h2spec.skip}</skip>

View File

@ -17,6 +17,7 @@ import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
@ -40,6 +41,7 @@ import org.eclipse.jetty.server.AliasCheck;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
@ -57,6 +59,7 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ClassLoaderDump;
import org.eclipse.jetty.util.component.DumpableAttributes;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.MountedPathResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.Resources;
@ -143,6 +146,7 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias
private File _tempDirectory;
private boolean _tempDirectoryPersisted = false;
private boolean _tempDirectoryCreated = false;
private boolean _createdTempDirectoryName = false;
private boolean _crossContextDispatchSupported = false;
public enum Availability
@ -236,6 +240,8 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias
if (isStarted())
throw new IllegalStateException("Started");
File oldTempDirectory = _tempDirectory;
if (tempDirectory != null)
{
try
@ -247,7 +253,23 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias
LOG.warn("Unable to find canonical path for {}", tempDirectory, e);
}
}
if (oldTempDirectory != null)
{
try
{
//if we had made up the name of the tmp directory previously, delete it if the new name is different
if (_createdTempDirectoryName && (tempDirectory == null || (!Files.isSameFile(oldTempDirectory.toPath(), tempDirectory.toPath()))))
IO.delete(oldTempDirectory);
}
catch (Exception e)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to delete old temp directory {}", oldTempDirectory, e);
}
}
_tempDirectory = tempDirectory;
_createdTempDirectoryName = false;
}
/**
@ -788,7 +810,187 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Alias
// if we're not persisting the temp dir contents delete it
if (tempDirectory != null && tempDirectory.exists() && !isTempDirectoryPersistent())
{
IO.delete(tempDirectory);
}
//if it was jetty that created the tmp dir, it can be reset, otherwise we need to retain the name
if (_createdTempDirectoryName)
{
setTempDirectory(null);
_createdTempDirectoryName = false;
}
}
/** Generate a reasonable name for the temp directory because one has not been
* explicitly configured by the user with {@link #setTempDirectory(File)}. The
* directory may also be created, if it is not persistent. If it is persistent
* it will be created as necessary by {@link #createTempDirectory()} later
* during the startup of the context.
*
* @throws Exception IllegalStateException if the parent tmp directory does
* not exist, or IOException if the child tmp directory cannot be created.
*/
protected void makeTempDirectory()
throws Exception
{
File parent = getServer().getContext().getTempDirectory();
if (parent == null || !parent.exists() || !parent.canWrite() || !parent.isDirectory())
throw new IllegalStateException("Parent for temp dir not configured correctly: " + (parent == null ? "null" : "writeable=" + parent.canWrite()));
boolean persistent = isTempDirectoryPersistent() || "work".equals(parent.toPath().getFileName().toString());
//Create a name for the temp dir
String temp = getCanonicalNameForTmpDir();
File tmpDir;
if (persistent)
{
//if it is to be persisted, make sure it will be the same name
//by not using File.createTempFile, which appends random digits
tmpDir = new File(parent, temp);
}
else
{
// ensure dir will always be unique by having classlib generate random path name
tmpDir = Files.createTempDirectory(parent.toPath(), temp).toFile();
tmpDir.deleteOnExit();
}
if (LOG.isDebugEnabled())
LOG.debug("Set temp dir {}", tmpDir);
setTempDirectory(tmpDir);
setTempDirectoryPersistent(persistent);
_createdTempDirectoryName = true;
}
/**
* Create a canonical name for a context temp directory.
* <p>
* The form of the name is:
*
* <pre>"jetty-"+host+"-"+port+"-"+resourceBase+"-_"+context+"-"+virtualhost+"-"+randomdigits+".dir"</pre>
*
* host and port uniquely identify the server
* context and virtual host uniquely identify the context
* randomdigits ensure every tmp directory is unique
*
* @return the canonical name for the context temp directory
*/
protected String getCanonicalNameForTmpDir()
{
StringBuilder canonicalName = new StringBuilder();
canonicalName.append("jetty-");
//get the host and the port from the first connector
Server server = getServer();
if (server != null)
{
Connector[] connectors = server.getConnectors();
if (connectors.length > 0)
{
//Get the host
String host = null;
int port = 0;
if (connectors[0] instanceof NetworkConnector connector)
{
host = connector.getHost();
port = connector.getLocalPort();
if (port < 0)
port = connector.getPort();
}
if (host == null)
host = "0.0.0.0";
canonicalName.append(host);
canonicalName.append("-");
canonicalName.append(port);
canonicalName.append("-");
}
}
// Resource base
try
{
Resource resource = getResourceForTempDirName();
String resourceBaseName = getBaseName(resource);
canonicalName.append(resourceBaseName);
canonicalName.append("-");
}
catch (Exception e)
{
if (LOG.isDebugEnabled())
LOG.debug("Can't get resource base name", e);
canonicalName.append("-"); // empty resourceBaseName segment
}
//Context name
String contextPath = getContextPath();
contextPath = contextPath.replace('/', '_');
contextPath = contextPath.replace('\\', '_');
canonicalName.append(contextPath);
//Virtual host (if there is one)
canonicalName.append("-");
List<String> vhosts = getVirtualHosts();
if (vhosts == null || vhosts.size() <= 0)
canonicalName.append("any");
else
canonicalName.append(vhosts.get(0));
// sanitize
for (int i = 0; i < canonicalName.length(); i++)
{
char c = canonicalName.charAt(i);
if (!Character.isJavaIdentifierPart(c) && "-.".indexOf(c) < 0)
canonicalName.setCharAt(i, '.');
}
canonicalName.append("-");
return StringUtil.sanitizeFileSystemName(canonicalName.toString());
}
/**
* @return the baseResource for the context to use in the temp dir name
*/
protected Resource getResourceForTempDirName()
{
return getBaseResource();
}
/**
* @param resource the resource whose filename minus suffix to extract
* @return the filename of the resource without suffix
*/
protected static String getBaseName(Resource resource)
{
// Use File System and File interface if present
Path resourceFile = resource.getPath();
if ((resourceFile != null) && (resource instanceof MountedPathResource))
{
resourceFile = ((MountedPathResource)resource).getContainerPath();
}
if (resourceFile != null)
{
Path fileName = resourceFile.getFileName();
return fileName == null ? "" : fileName.toString();
}
// Use URI itself.
URI uri = resource.getURI();
if (uri == null)
{
if (LOG.isDebugEnabled())
{
LOG.debug("Resource has no URI reference: {}", resource);
}
return "";
}
return URIUtil.getUriLastPathSegment(uri);
}
public boolean checkVirtualHost(Request request)

View File

@ -324,11 +324,7 @@ public class GzipResponseAndCallback extends Response.Wrapper implements Callbac
@Override
protected void onCompleteFailure(Throwable x)
{
if (_deflaterEntry != null)
{
_deflaterEntry.release();
_deflaterEntry = null;
}
cleanup();
super.onCompleteFailure(x);
}
@ -381,6 +377,7 @@ public class GzipResponseAndCallback extends Response.Wrapper implements Callbac
{
if (_deflaterEntry != null)
{
_state.set(GZState.FINISHED);
_deflaterEntry.release();
_deflaterEntry = null;
}

View File

@ -49,6 +49,7 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.server.FormFields;
import org.eclipse.jetty.server.Handler;
@ -59,6 +60,7 @@ import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenPaths;
@ -194,6 +196,64 @@ public class GzipHandlerTest
_gzipHandler.setHandler(_contextHandler);
}
@Test
public void testFailureDuringGzipWrite() throws Exception
{
Handler leafHandler = new Handler.Abstract()
{
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
try (var out = Content.Sink.asOutputStream(response))
{
out.write("Hello, Jetty".getBytes(StandardCharsets.UTF_8));
}
return true;
}
};
Handler rootHandler = new Handler.Wrapper(new GzipHandler(leafHandler))
{
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
return super.handle(request, new Response.Wrapper(request, response)
{
@Override
public void write(boolean last, ByteBuffer byteBuffer, Callback callback)
{
throw new ArithmeticException("expected");
}
}, callback);
}
};
_server.setHandler(rootHandler);
ErrorHandler errorHandler = new ErrorHandler();
errorHandler.setShowStacks(true);
errorHandler.setShowCauses(true);
_server.setErrorHandler(errorHandler);
_server.start();
// generated and parsed test
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
request.setMethod("GET");
request.setURI("/");
request.setVersion("HTTP/1.0");
request.setHeader("Host", "tester");
request.setHeader("accept-encoding", "gzip");
try (StacklessLogging ignore = new StacklessLogging(Response.class))
{
response = HttpTester.parseResponse(_connector.getResponse(request.generate()));
}
assertThat(response.getStatus(), is(500));
String content = response.getContent();
assertThat(content, containsString("ArithmeticException: expected"));
assertThat(content, not(containsString("Suppressed: ")));
}
@Test
public void testAddIncludePaths()
{

View File

@ -191,7 +191,7 @@ public class Fields implements Iterable<Fields.Field>
public void put(String name, String value)
{
// Preserve the case for the field name
Field field = new Field(name, value);
Field field = new Field(name, StringUtil.nonNull(value));
fields.put(name, field);
}
@ -222,9 +222,9 @@ public class Fields implements Iterable<Fields.Field>
{
if (f == null)
// Preserve the case for the field name
return new Field(name, value);
return new Field(name, StringUtil.nonNull(value));
else
return new Field(f.getName(), f.getValues(), value);
return new Field(f.getName(), f.getValues(), StringUtil.nonNull(value));
});
}
@ -246,9 +246,9 @@ public class Fields implements Iterable<Fields.Field>
fields.compute(name, (k, f) ->
{
if (f == null)
return new Field(name, List.of(values));
return new Field(name, StringUtil.toListNonNull(values));
else
return new Field(f.getName(), f.getValues(), List.of(values));
return new Field(f.getName(), f.getValues(), StringUtil.toListNonNull(values));
});
}
}

View File

@ -540,6 +540,23 @@ public class StringUtil
return s;
}
/**
* Convert an array of strings to a list of non-null strings.
*
* @param strings the array
* @return The list of non-null strings.
* @see #nonNull(String)
*/
public static List<String> toListNonNull(String... strings)
{
List<String> result = new ArrayList<>(strings.length);
for (String s : strings)
{
result.add(nonNull(s));
}
return result;
}
public static boolean equals(String s, char[] buf, int offset, int length)
{
if (s.length() != length)

View File

@ -82,4 +82,18 @@ public class FieldsTest
assertThat(set, containsInAnyOrder("x", "y", "z"));
}
@Test
public void testNullValues()
{
Fields fields = new Fields();
fields.add("x", (String)null);
fields.add("y", "1", null, "2");
fields.put("z", null);
assertThat(fields.getSize(), equalTo(3));
assertThat(fields.getValues("x"), contains(""));
assertThat(fields.getValues("y"), contains("1", "", "2"));
assertThat(fields.getValues("z"), contains(""));
}
}

View File

@ -441,6 +441,70 @@ public class CreationTest
}
}
/**
* Test creating a session in a request and then invalidating it in another request
*/
@Test
public void testSessionSimpleCreateInvalidate() throws Exception
{
String contextPath = "";
String servletMapping = "/server";
int inactivePeriod = -1; //immortal session
int scavengePeriod = 3;
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT);
cacheFactory.setSaveOnCreate(true);
cacheFactory.setFlushOnResponseCommit(true); //ensure session saved before response returned
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
SessionTestSupport server1 = new SessionTestSupport(0, inactivePeriod, scavengePeriod, cacheFactory, storeFactory);
TestServlet servlet = new TestServlet();
ServletHolder holder = new ServletHolder(servlet);
ServletContextHandler contextHandler = server1.addContext(contextPath);
contextHandler.addServlet(holder, servletMapping);
servlet.setStore(contextHandler.getSessionHandler().getSessionCache().getSessionDataStore());
server1.start();
int port1 = server1.getPort();
try (StacklessLogging stackless = new StacklessLogging(CreationTest.class.getPackage()))
{
HttpClient client = new HttpClient();
client.start();
String url = "http://localhost:" + port1 + contextPath + servletMapping + "?action=create";
//make a request to set up a session on the server
ContentResponse response = client.GET(url);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
//Ensure session handling is finished
Awaitility.waitAtMost(5, TimeUnit.SECONDS).until(() -> contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().exists(servlet._id));
//make a request to re-obtain the session on the server
Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=test");
response = request.send();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
Awaitility.waitAtMost(5, TimeUnit.SECONDS).until(() -> contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().exists(servlet._id));
//at this point the last accessed time should be the creation time
long lastAccessedTime = servlet._lastAccessedTime;
assertEquals(lastAccessedTime, servlet._creationTime);
//make another request to re-obtain the session on the server
request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=test");
response = request.send();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
Awaitility.waitAtMost(5, TimeUnit.SECONDS).until(() -> contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().exists(servlet._id));
//check that the lastAccessedTime is being updated
assertTrue(lastAccessedTime < servlet._lastAccessedTime);
//make a request to invalidate it
request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=invalidate");
response = request.send();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
}
finally
{
server1.stop();
}
}
public static class MySessionListener implements HttpSessionListener
{
@Override
@ -460,6 +524,8 @@ public class CreationTest
private static final long serialVersionUID = 1L;
public String _id = null;
public SessionDataStore _store;
public long _lastAccessedTime;
public long _creationTime;
public void setStore(SessionDataStore store)
{
@ -475,6 +541,7 @@ public class CreationTest
case "forward" ->
{
HttpSession session = createAndSaveSessionId(request);
assertTrue(session.isNew());
ServletContext contextB = getServletContext().getContext("/contextB");
RequestDispatcher dispatcherB = contextB.getRequestDispatcher(request.getServletPath());
dispatcherB.forward(request, httpServletResponse);
@ -488,7 +555,7 @@ public class CreationTest
case "forwardc" ->
{
HttpSession session = createAndSaveSessionId(request);
assertTrue(session.isNew());
//forward to contextC
ServletContext contextC = getServletContext().getContext("/contextC");
RequestDispatcher dispatcherC = contextC.getRequestDispatcher(request.getServletPath());
@ -498,7 +565,7 @@ public class CreationTest
{
HttpSession session = createAndSaveSessionId(request);
assertNotNull(session);
assertTrue(session.isNew());
ServletContext contextB = getServletContext().getContext("/contextB");
RequestDispatcher dispatcherB = contextB.getRequestDispatcher(request.getServletPath());
dispatcherB.forward(request, httpServletResponse);
@ -509,14 +576,27 @@ public class CreationTest
assertNotNull(_id);
HttpSession session = request.getSession(false);
assertNotNull(session);
_lastAccessedTime = session.getLastAccessedTime();
assertFalse(session.isNew());
assertNotNull(session.getAttribute("value")); //check we see our previous session
assertNull(session.getAttribute("B")); //check we don't see stuff from other contexts
assertNull(session.getAttribute("C"));
}
case "invalidate" ->
{
HttpSession session = request.getSession(false);
assertNotNull(session);
_id = session.getId();
assertFalse(session.isNew());
session.invalidate();
assertNull(request.getSession(false));
}
case "create", "createinv", "createinvcreate" ->
{
currentRequest.set(request);
HttpSession session = createAndSaveSessionId(request);
assertTrue(session.isNew());
_creationTime = session.getCreationTime();
String check = request.getParameter("check");
if (!StringUtil.isBlank(check) && _store != null)
{

View File

@ -1491,6 +1491,40 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
return _metadata;
}
@Override
protected void makeTempDirectory() throws Exception
{
super.makeTempDirectory();
}
@Override
protected String getCanonicalNameForTmpDir()
{
return super.getCanonicalNameForTmpDir();
}
/**
* If the webapp has no baseresource yet, use
* the war to make the temp directory name.
*
* @return the baseresource if non null, or the war
*/
@Override
protected Resource getResourceForTempDirName()
{
Resource resource = super.getResourceForTempDirName();
if (resource == null)
{
if (getWar() == null || getWar().length() == 0)
throw new IllegalStateException("No resourceBase or war set for context");
// Use name of given resource in the temporary dirname
resource = newResource(getWar());
}
return resource;
}
/**
* Add a Server Class pattern to use for all WebAppContexts.
* @param server The {@link Server} instance to add classes to

View File

@ -39,7 +39,6 @@ public class WebInfConfiguration extends AbstractConfiguration
{
private static final Logger LOG = LoggerFactory.getLogger(WebInfConfiguration.class);
public static final String TEMPDIR_CONFIGURED = "org.eclipse.jetty.tmpdirConfigured";
public static final String TEMPORARY_RESOURCE_BASE = "org.eclipse.jetty.webapp.tmpResourceBase";
public static final String ORIGINAL_RESOURCE_BASE = "org.eclipse.jetty.webapp.originalResourceBase";
@ -89,10 +88,6 @@ public class WebInfConfiguration extends AbstractConfiguration
@Override
public void deconfigure(WebAppContext context) throws Exception
{
//if it wasn't explicitly configured by the user, then unset it
if (!(context.getAttribute(TEMPDIR_CONFIGURED) instanceof Boolean tmpdirConfigured && tmpdirConfigured))
context.setTempDirectory(null);
//reset the base resource back to what it was before we did any unpacking of resources
Resource originalBaseResource = (Resource)context.removeAttribute(ORIGINAL_RESOURCE_BASE);
context.setBaseResource(originalBaseResource);
@ -133,7 +128,6 @@ public class WebInfConfiguration extends AbstractConfiguration
File tempDirectory = context.getTempDirectory();
if (tempDirectory != null)
{
context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE); //the tmp dir was set explicitly
return;
}
@ -148,37 +142,14 @@ public class WebInfConfiguration extends AbstractConfiguration
return;
}
makeTempDirectory(context.getServer().getContext().getTempDirectory(), context);
context.makeTempDirectory();
}
@Deprecated (forRemoval = true, since = "12.0.12")
public void makeTempDirectory(File parent, WebAppContext context)
throws Exception
{
if (parent == null || !parent.exists() || !parent.canWrite() || !parent.isDirectory())
throw new IllegalStateException("Parent for temp dir not configured correctly: " + (parent == null ? "null" : "writeable=" + parent.canWrite()));
boolean persistent = context.isTempDirectoryPersistent() || "work".equals(parent.toPath().getFileName().toString());
//Create a name for the webapp
String temp = getCanonicalNameForWebAppTmpDir(context);
File tmpDir;
if (persistent)
{
//if it is to be persisted, make sure it will be the same name
//by not using File.createTempFile, which appends random digits
tmpDir = new File(parent, temp);
}
else
{
// ensure dir will always be unique by having classlib generate random path name
tmpDir = Files.createTempDirectory(parent.toPath(), temp).toFile();
tmpDir.deleteOnExit();
}
if (LOG.isDebugEnabled())
LOG.debug("Set temp dir {}", tmpDir);
context.setTempDirectory(tmpDir);
context.setTempDirectoryPersistent(persistent);
context.makeTempDirectory();
}
public void unpack(WebAppContext context) throws IOException
@ -395,91 +366,20 @@ public class WebInfConfiguration extends AbstractConfiguration
*
* @param context the context to get the canonical name from
* @return the canonical name for the webapp temp directory
* @deprecated this method is no longer used
*/
@Deprecated(forRemoval = true, since = "12.0.12")
public static String getCanonicalNameForWebAppTmpDir(WebAppContext context)
{
StringBuilder canonicalName = new StringBuilder();
canonicalName.append("jetty-");
//get the host and the port from the first connector
Server server = context.getServer();
if (server != null)
{
Connector[] connectors = server.getConnectors();
if (connectors.length > 0)
{
//Get the host
String host = null;
int port = 0;
if (connectors[0] instanceof NetworkConnector connector)
{
host = connector.getHost();
port = connector.getLocalPort();
if (port < 0)
port = connector.getPort();
}
if (host == null)
host = "0.0.0.0";
canonicalName.append(host);
canonicalName.append("-");
canonicalName.append(port);
canonicalName.append("-");
}
}
// Resource base
try
{
Resource resource = context.getBaseResource();
if (resource == null)
{
if (context.getWar() == null || context.getWar().length() == 0)
throw new IllegalStateException("No resourceBase or war set for context");
// Set dir or WAR to resource
resource = context.newResource(context.getWar());
}
String resourceBaseName = getResourceBaseName(resource);
canonicalName.append(resourceBaseName);
canonicalName.append("-");
}
catch (Exception e)
{
if (LOG.isDebugEnabled())
LOG.debug("Can't get resource base name", e);
canonicalName.append("-"); // empty resourceBaseName segment
}
//Context name
String contextPath = context.getContextPath();
contextPath = contextPath.replace('/', '_');
contextPath = contextPath.replace('\\', '_');
canonicalName.append(contextPath);
//Virtual host (if there is one)
canonicalName.append("-");
List<String> vhosts = context.getVirtualHosts();
if (vhosts == null || vhosts.size() <= 0)
canonicalName.append("any");
else
canonicalName.append(vhosts.get(0));
// sanitize
for (int i = 0; i < canonicalName.length(); i++)
{
char c = canonicalName.charAt(i);
if (!Character.isJavaIdentifierPart(c) && "-.".indexOf(c) < 0)
canonicalName.setCharAt(i, '.');
}
canonicalName.append("-");
return StringUtil.sanitizeFileSystemName(canonicalName.toString());
return context.getCanonicalNameForTmpDir();
}
/**
* @param resource the Resource for which to extract a short name
* @return extract a short name for the resource
* @deprecated this method is no longer needed
*/
@Deprecated(forRemoval = true, since = "12.0.12")
protected static String getResourceBaseName(Resource resource)
{
// Use File System and File interface if present

View File

@ -31,7 +31,6 @@ import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.IO;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
@ -40,6 +39,8 @@ import org.junit.jupiter.params.provider.ValueSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@ExtendWith(WorkDirExtension.class)
public class TempDirTest
@ -174,11 +175,11 @@ public class TempDirTest
_server.start();
File tempDirectory = webAppContext.getTempDirectory();
webAppContext.stop();
assertNull(webAppContext.getTempDirectory());
webAppContext.start();
assertThat(tempDirectory.toPath(), not(PathMatchers.isSame(webAppContext.getTempDirectory().toPath())));
}
@Disabled ("Enable after issue 11548 fixed")
@Test
public void testSameTempDir(WorkDir workDir) throws Exception
{
@ -209,7 +210,38 @@ public class TempDirTest
File tempDirectory = webAppContext.getTempDirectory();
assertThat(tempDirectory.toPath(), PathMatchers.isSame(configuredTmpDir));
webAppContext.stop();
assertNotNull(webAppContext.getTempDirectory());
webAppContext.start();
assertThat(tempDirectory.toPath(), PathMatchers.isSame(webAppContext.getTempDirectory().toPath()));
}
}
@Test
public void testTempDirDeleted(WorkDir workDir) throws Exception
{
// Create war on the fly
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
Path warFile = workDir.getEmptyPathDir().resolve("test.war");
Map<String, String> env = new HashMap<>();
env.put("create", "true");
URI uri = URI.create("jar:" + warFile.toUri().toASCIIString());
// Use ZipFS so that we can create paths that are just "/"
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
{
Path root = zipfs.getPath("/");
IO.copyDir(testWebappDir, root);
}
_server = new Server();
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/");
webAppContext.setWarResource(webAppContext.getResourceFactory().newResource(warFile));
_server.setHandler(webAppContext);
_server.start();
File tempDirectory = webAppContext.getTempDirectory();
_server.stop();
assertThat("Temp dir exists", !Files.exists(tempDirectory.toPath()));
assertNull(webAppContext.getTempDirectory());
}
}

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- // -->
<!-- // ======================================================================== -->
<!-- // Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. -->
<!-- // -->
<!-- // This program and the accompanying materials are made available under the -->
<!-- // terms of the Eclipse Public License v. 2.0 which is available at -->
<!-- // https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -->
<!-- // which is available at https://www.apache.org/licenses/LICENSE-2.0. -->
<!-- // -->
<!-- // SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -->
<!-- // ======================================================================== -->
<!-- // -->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
</web-app>

View File

@ -2673,6 +2673,24 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
installBean(ContextHandler.this, true);
}
@Override
public void makeTempDirectory() throws Exception
{
super.makeTempDirectory();
}
@Override
public String getCanonicalNameForTmpDir()
{
return super.getCanonicalNameForTmpDir();
}
@Override
public Resource getResourceForTempDirName()
{
return super.getResourceForTempDirName();
}
@Override
public void setContextPath(String contextPath)
{

View File

@ -632,7 +632,7 @@ public class MultiPartFormInputStream implements MultiPart.Parser
if (parser.getState() != MultiPartParser.State.END)
{
if (parser.getState() == MultiPartParser.State.PREAMBLE)
_err = new IOException("Missing initial multi part boundary");
_err = new IOException("Missing content for multipart request");
else
_err = new IOException("Incomplete Multipart");
}

View File

@ -65,10 +65,11 @@ import org.slf4j.LoggerFactory;
*
* @deprecated Replaced by {@link MultiPartFormInputStream}.
* This code is slower and subject to more bugs than its replacement {@link MultiPartFormInputStream}. However,
* this class accepts formats non-compliant the RFC that the new {@link MultiPartFormInputStream} does not accept.
* this class accepts non-compliant RFC formats that the new {@link MultiPartFormInputStream} does not accept.
* This class is unavailable on <em>ee10</em> and newer environments.
*/
@Deprecated
class MultiPartInputStreamLegacyParser implements MultiPart.Parser
@Deprecated (forRemoval = true, since = "10.0.10")
public class MultiPartInputStreamLegacyParser implements MultiPart.Parser
{
private static final Logger LOG = LoggerFactory.getLogger(MultiPartInputStreamLegacyParser.class);
public static final MultipartConfigElement __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir"));
@ -598,7 +599,7 @@ class MultiPartInputStreamLegacyParser implements MultiPart.Parser
}
if (line == null || line.length() == 0)
throw new IOException("Missing initial multi part boundary");
throw new IOException("Missing content for multipart request");
// Empty multipart.
if (line.equals(lastBoundary))
@ -699,7 +700,7 @@ class MultiPartInputStreamLegacyParser implements MultiPart.Parser
// Check if we can create a new part.
_numParts++;
if (_maxParts >= 0 && _numParts > _maxParts)
throw new IllegalStateException(String.format("Form with too many parts [%d > %d]", _numParts, _maxParts));
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]", _numParts, _maxParts));
//Have a new Part
MultiPart part = new MultiPart(name, filename);
@ -863,7 +864,7 @@ class MultiPartInputStreamLegacyParser implements MultiPart.Parser
MultiPartCompliance.Violation.LF_LINE_TERMINATION, "0x10"));
}
else
throw new IOException("Incomplete parts");
throw new IOException("Incomplete Multipart");
}
catch (Exception e)
{

View File

@ -515,7 +515,7 @@ public class Request implements HttpServletRequest
String msg = "Unable to extract content parameters";
if (LOG.isDebugEnabled())
LOG.debug(msg, e);
throw new RuntimeIOException(msg, e);
throw new BadMessageException(msg, e);
}
}
}
@ -2044,7 +2044,21 @@ public class Request implements HttpServletRequest
MultiPartCompliance multiPartCompliance = getHttpChannel().getHttpConfiguration().getMultiPartCompliance();
_multiParts = newMultiParts(multiPartCompliance, config, maxFormKeys);
Collection<Part> parts = _multiParts.getParts();
Collection<Part> parts;
try
{
parts = _multiParts.getParts();
}
catch (BadMessageException e)
{
throw e;
}
// Catch RuntimeException to handle IllegalStateException, IllegalArgumentException, CharacterEncodingException, etc .. (long list)
catch (RuntimeException | IOException e)
{
throw new BadMessageException("Unable to parse form content", e);
}
reportComplianceViolations();
String formCharset = null;

View File

@ -520,7 +520,10 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab
coreRequest.setSessionManager(_sessionManager);
coreRequest.setRequestedSession(currentRequestedSession);
if (currentRequestedSession != null)
{
coreRequest.setManagedSession(currentRequestedSession.session());
currentSession = currentRequestedSession.session();
}
break;
}
case ASYNC:

View File

@ -13,9 +13,12 @@
package org.eclipse.jetty.ee9.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@ -28,6 +31,7 @@ import org.eclipse.jetty.client.AsyncRequestContent;
import org.eclipse.jetty.client.ContentResponse;
import org.eclipse.jetty.client.FormRequestContent;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.StringRequestContent;
import org.eclipse.jetty.ee9.nested.ContextHandler;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
@ -42,6 +46,8 @@ 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.assertEquals;
public class FormTest
@ -229,4 +235,98 @@ public class FormTest
assertEquals(HttpStatus.OK_200, response.getStatus());
}
public static Stream<Arguments> invalidForm()
{
return Stream.of(
Arguments.of("%A", "java.lang.IllegalArgumentException: Not valid encoding &apos;%A?&apos;"),
Arguments.of("name%", "java.lang.IllegalArgumentException: Not valid encoding &apos;%??&apos;"),
Arguments.of("name%A", "java.lang.IllegalArgumentException: Not valid encoding &apos;%A?&apos;"),
Arguments.of("name%A&", "java.lang.IllegalArgumentException: Not valid encoding &apos;%A&amp;&apos;"),
Arguments.of("name=%", "java.lang.IllegalArgumentException: Not valid encoding &apos;%??&apos;"),
Arguments.of("name=A%%A", "java.lang.IllegalArgumentException: Not valid encoding &apos;%%A&apos;"),
Arguments.of("name=A%%3D", "java.lang.IllegalArgumentException: Not valid encoding &apos;%%3&apos;"),
Arguments.of("%=", "java.lang.IllegalArgumentException: Not valid encoding &apos;%=?&apos;"),
Arguments.of("name=%A", "java.lang.IllegalArgumentException: Not valid encoding &apos;%A?&apos;"),
Arguments.of("name=value%A", "ava.lang.IllegalArgumentException: Not valid encoding &apos;%A?&apos;"),
Arguments.of("n%AH=v", "java.lang.IllegalArgumentException: Not valid encoding &apos;%AH&apos;"),
Arguments.of("n=v%AH", "java.lang.IllegalArgumentException: Not valid encoding &apos;%AH&apos;")
);
}
@ParameterizedTest
@MethodSource("invalidForm")
public void testContentTypeWithoutCharsetDecodeBadUTF8(String rawForm, String expectedCauseMessage) throws Exception
{
start(handler -> new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
{
// This is expected to throw an exception due to the bad form input
request.getParameterMap();
}
});
StringRequestContent requestContent = new StringRequestContent("application/x-www-form-urlencoded", rawForm);
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.method(HttpMethod.POST)
.path(contextPath + servletPath)
.body(requestContent)
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.BAD_REQUEST_400, response.getStatus(), response::getContentAsString);
String responseContent = response.getContentAsString();
assertThat(responseContent, containsString("Unable to parse form content"));
assertThat(responseContent, containsString(expectedCauseMessage));
}
public static Stream<Arguments> utf8Form()
{
return Stream.of(
Arguments.of("euro=%E2%82%AC", List.of("param[euro] = \"\"")),
Arguments.of("name=%AB%CD", List.of("param[name] = \"<EFBFBD><EFBFBD>\"")),
Arguments.of("name=x%AB%CDz", List.of("param[name] = \"x<EFBFBD><EFBFBD>z\"")),
Arguments.of("name=%FF%FF%FF%FF", List.of("param[name] = \"<EFBFBD><EFBFBD><EFBFBD><EFBFBD>\""))
);
}
@ParameterizedTest
@MethodSource("utf8Form")
public void testUtf8Decoding(String rawForm, List<String> expectedParams) throws Exception
{
start(handler -> new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
{
response.setCharacterEncoding("utf-8");
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
Map<String, String[]> paramMap = request.getParameterMap();
List<String> names = paramMap.keySet().stream().sorted().toList();
for (String name: names)
{
out.printf("param[%s] = \"%s\"%n", name, String.join(",", paramMap.get(name)));
}
}
});
StringRequestContent requestContent = new StringRequestContent("application/x-www-form-urlencoded", rawForm);
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.method(HttpMethod.POST)
.path(contextPath + servletPath)
.body(requestContent)
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.OK_200, response.getStatus(), response::getContentAsString);
String responseContent = response.getContentAsString();
for (String expectedParam: expectedParams)
assertThat(responseContent, containsString(expectedParam));
}
}

View File

@ -15,6 +15,7 @@ package org.eclipse.jetty.ee9.servlet;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@ -24,6 +25,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import jakarta.servlet.MultipartConfigElement;
@ -49,15 +51,20 @@ import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.MultiPart;
import org.eclipse.jetty.http.MultiPartCompliance;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.util.IO;
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.containsString;
@ -98,18 +105,27 @@ public class MultiPartServletTest
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/plain");
PrintWriter out = resp.getWriter();
if (!req.getContentType().contains(MimeTypes.Type.MULTIPART_FORM_DATA.asString()))
{
resp.setContentType("text/plain");
resp.getWriter().println("not content type " + MimeTypes.Type.MULTIPART_FORM_DATA);
resp.getWriter().println("contentType: " + req.getContentType());
out.println("not content type " + MimeTypes.Type.MULTIPART_FORM_DATA);
out.println("contentType: " + req.getContentType());
return;
}
resp.setContentType("text/plain");
for (Part part : req.getParts())
{
resp.getWriter().println("Part: name=" + part.getName() + ", size=" + part.getSize());
out.printf("Part: name=%s, size=%s", part.getName(), part.getSize());
if (part.getSize() <= 100)
{
String content = IO.toString(part.getInputStream());
out.printf(", content=%s", content);
}
out.println();
}
}
}
@ -130,14 +146,15 @@ public class MultiPartServletTest
}
}
@BeforeEach
public void start() throws Exception
private void startServer(MultiPartCompliance multiPartCompliance) throws Exception
{
tmpDir = Files.createTempDirectory(MultiPartServletTest.class.getSimpleName());
assertNotNull(tmpDir);
server = new Server();
connector = new ServerConnector(server);
HttpConfiguration httpConfiguration = new HttpConfiguration();
httpConfiguration.setMultiPartCompliance(multiPartCompliance);
connector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
server.addConnector(connector);
MultipartConfigElement config = new MultipartConfigElement(tmpDir.toAbsolutePath().toString(),
@ -180,9 +197,50 @@ public class MultiPartServletTest
IO.delete(tmpDir.toFile());
}
@Test
public void testLargePart() throws Exception
public static Stream<Arguments> multipartModes()
{
return Stream.of(
Arguments.of(MultiPartCompliance.RFC7578),
Arguments.of(MultiPartCompliance.LEGACY)
);
}
/**
* The request indicates that it is a multipart/form-data, but no body is sent.
*/
@ParameterizedTest
@MethodSource("multipartModes")
public void testEmptyBodyMultipartForm(MultiPartCompliance multiPartCompliance) throws Exception
{
startServer(multiPartCompliance);
String contentType = "multipart/form-data; boundary=---------------boundaryXYZ123";
StringRequestContent emptyContent = new StringRequestContent(contentType, "");
InputStreamResponseListener listener = new InputStreamResponseListener();
client.newRequest("localhost", connector.getLocalPort())
.path("/defaultConfig")
.scheme(HttpScheme.HTTP.asString())
.method(HttpMethod.POST)
.body(emptyContent)
.send(listener);
Response response = listener.get(60, TimeUnit.SECONDS);
assertThat(response.getStatus(), equalTo(HttpStatus.BAD_REQUEST_400));
assert400orEof(listener, responseContent ->
{
assertThat(responseContent, containsString("Unable to parse form content"));
assertThat(responseContent, containsString("Missing content for multipart request"));
});
}
@ParameterizedTest
@MethodSource("multipartModes")
public void testLargePart(MultiPartCompliance multiPartCompliance) throws Exception
{
startServer(multiPartCompliance);
OutputStreamRequestContent content = new OutputStreamRequestContent();
MultiPartRequestContent multiPart = new MultiPartRequestContent();
multiPart.addPart(new MultiPart.ContentSourcePart("param", null, null, content));
@ -212,9 +270,248 @@ public class MultiPartServletTest
});
}
@Test
public void testManyParts() throws Exception
@ParameterizedTest
@MethodSource("multipartModes")
public void testIncompleteMultipart(MultiPartCompliance multiPartCompliance) throws Exception
{
startServer(multiPartCompliance);
String contentType = "multipart/form-data; boundary=-------------------------7e21c038151054";
String incompleteForm = """
---------------------------7e21c038151054
Content-Disposition: form-data; name="description"
Some data, but incomplete
---------------------------7e21c038151054
Content-Disposition: form-d"""; // intentionally incomplete
StringRequestContent incomplete = new StringRequestContent(
contentType,
incompleteForm
);
InputStreamResponseListener listener = new InputStreamResponseListener();
client.newRequest("localhost", connector.getLocalPort())
.path("/defaultConfig")
.scheme(HttpScheme.HTTP.asString())
.method(HttpMethod.POST)
.body(incomplete)
.send(listener);
assert400orEof(listener, responseContent ->
{
assertThat(responseContent, containsString("Unable to parse form content"));
assertThat(responseContent, containsString("Incomplete Multipart"));
});
}
@ParameterizedTest
@MethodSource("multipartModes")
public void testLineFeedCarriageReturnEOL(MultiPartCompliance multiPartCompliance) throws Exception
{
startServer(multiPartCompliance);
String contentType = "multipart/form-data; boundary=---------------------------7e25e1e151054";
// NOTE: The extra `\r` here are intentional, do not remove.
String rawForm = """
-----------------------------7e25e1e151054\r
Content-Disposition: form-data; name="user"\r
\r
anotheruser\r
-----------------------------7e25e1e151054\r
Content-Disposition: form-data; name="comment"\r
\r
with something to say\r
-----------------------------7e25e1e151054--\r
""";
StringRequestContent form = new StringRequestContent(
contentType,
rawForm
);
InputStreamResponseListener listener = new InputStreamResponseListener();
client.newRequest("localhost", connector.getLocalPort())
.path("/defaultConfig")
.scheme(HttpScheme.HTTP.asString())
.method(HttpMethod.POST)
.body(form)
.send(listener);
assert400orEof(listener, responseContent ->
{
assertThat(responseContent, containsString("Unable to parse form content"));
if (multiPartCompliance == MultiPartCompliance.RFC7578)
{
assertThat(responseContent, containsString("Illegal character ALPHA=&apos;s&apos"));
}
else if (multiPartCompliance == MultiPartCompliance.LEGACY)
{
assertThat(responseContent, containsString("Incomplete Multipart"));
}
});
}
@ParameterizedTest
@MethodSource("multipartModes")
public void testAllWhitespaceForm(MultiPartCompliance multiPartCompliance) throws Exception
{
startServer(multiPartCompliance);
String contentType = "multipart/form-data; boundary=----WebKitFormBoundaryjwqONTsAFgubfMZc";
String rawForm = " \n \n \n \n \n \n \n \n \n ";
StringRequestContent form = new StringRequestContent(
contentType,
rawForm
);
InputStreamResponseListener listener = new InputStreamResponseListener();
client.newRequest("localhost", connector.getLocalPort())
.path("/defaultConfig")
.scheme(HttpScheme.HTTP.asString())
.method(HttpMethod.POST)
.body(form)
.send(listener);
assert400orEof(listener, responseContent ->
{
assertThat(responseContent, containsString("Unable to parse form content"));
assertThat(responseContent, containsString("Missing content for multipart request"));
});
}
/**
* A part with Content-Transfer-Encoding: base64, and the content is valid Base64 encoded.
*
* MultiPartCompliance mode set to allow MultiPartCompliance.Violation.BASE64_TRANSFER_ENCODING
*/
@Test
public void testLegacyContentTransferEncodingBase64Allowed() throws Exception
{
MultiPartCompliance legacyBase64 = MultiPartCompliance.from("LEGACY,BASE64_TRANSFER_ENCODING");
startServer(legacyBase64);
String contentType = "multipart/form-data; boundary=8GbcZNTauFWYMt7GeM9BxFMdlNBJ6aLJhGdXp";
String rawForm = """
--8GbcZNTauFWYMt7GeM9BxFMdlNBJ6aLJhGdXp
Content-ID: <foo@example.org>
Content-Disposition: form-data; name="quote"
Content-Transfer-Encoding: base64
IkJvb2tzIGFyZSB0aGUgbGliZXJhdGVkIHNwaXJpdHMgb2YgbWVuLiIgLS0gTWFyayBUd2Fpbg==
--8GbcZNTauFWYMt7GeM9BxFMdlNBJ6aLJhGdXp--
""";
StringRequestContent form = new StringRequestContent(
contentType,
rawForm
);
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.path("/")
.scheme(HttpScheme.HTTP.asString())
.method(HttpMethod.POST)
.body(form)
.send();
assertEquals(200, response.getStatus());
assertThat(response.getContentAsString(), containsString("Part: name=quote, size=55, content=\"Books are the liberated spirits of men.\" -- Mark Twain"));
}
/**
* A part with Content-Transfer-Encoding: base64, but the content is not actually encoded in Base 64.
*
* MultiPartCompliance mode set to allow MultiPartCompliance.Violation.BASE64_TRANSFER_ENCODING
*/
@Test
public void testLegacyContentTransferEncodingBadBase64Allowed() throws Exception
{
MultiPartCompliance legacyBase64 = MultiPartCompliance.from("LEGACY,BASE64_TRANSFER_ENCODING");
startServer(legacyBase64);
String contentType = "multipart/form-data; boundary=8GbcZNTauFWYMt7GeM9BxFMdlNBJ6aLJhGdXp";
String rawForm = """
--8GbcZNTauFWYMt7GeM9BxFMdlNBJ6aLJhGdXp
Content-ID: <foo@example.org>
Content-Disposition: form-data; name="quote"
Content-Transfer-Encoding: base64
"Travel is fatal to prejudice." -- Mark Twain
--8GbcZNTauFWYMt7GeM9BxFMdlNBJ6aLJhGdXp--
""";
StringRequestContent form = new StringRequestContent(
contentType,
rawForm
);
InputStreamResponseListener listener = new InputStreamResponseListener();
client.newRequest("localhost", connector.getLocalPort())
.path("/")
.scheme(HttpScheme.HTTP.asString())
.method(HttpMethod.POST)
.body(form)
.send(listener);
assert400orEof(listener, responseContent ->
{
assertThat(responseContent, containsString("Unable to parse form content"));
assertThat(responseContent, containsString("java.lang.IllegalArgumentException: Last unit does not have enough valid bits"));
});
}
/**
* A part with Content-Transfer-Encoding: base64, and the content is valid Base64 encoded.
*
* MultiPartCompliance mode set to allow MultiPartCompliance.LEGACY, which does not perform
* base64 decoding.
*/
@Test
public void testLegacyContentTransferEncodingBase64() throws Exception
{
MultiPartCompliance legacyBase64 = MultiPartCompliance.LEGACY;
startServer(legacyBase64);
String contentType = "multipart/form-data; boundary=8GbcZNTauFWYMt7GeM9BxFMdlNBJ6aLJhGdXp";
String rawForm = """
--8GbcZNTauFWYMt7GeM9BxFMdlNBJ6aLJhGdXp
Content-ID: <foo@example.org>
Content-Disposition: form-data; name="quote"
Content-Transfer-Encoding: base64
IkJvb2tzIGFyZSB0aGUgbGliZXJhdGVkIHNwaXJpdHMgb2YgbWVuLiIgLS0gTWFyayBUd2Fpbg==
--8GbcZNTauFWYMt7GeM9BxFMdlNBJ6aLJhGdXp--
""";
StringRequestContent form = new StringRequestContent(
contentType,
rawForm
);
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.path("/")
.scheme(HttpScheme.HTTP.asString())
.method(HttpMethod.POST)
.body(form)
.send();
assertEquals(200, response.getStatus());
assertThat(response.getContentAsString(), containsString("Part: name=quote, size=76, content=IkJvb2tzIGFyZSB0aGUgbGliZXJhdGVkIHNwaXJpdHMgb2YgbWVuLiIgLS0gTWFyayBUd2Fpbg=="));
}
@ParameterizedTest
@MethodSource("multipartModes")
public void testManyParts(MultiPartCompliance multiPartCompliance) throws Exception
{
startServer(multiPartCompliance);
byte[] byteArray = new byte[1024];
Arrays.fill(byteArray, (byte)1);
@ -241,9 +538,12 @@ public class MultiPartServletTest
});
}
@Test
public void testMaxRequestSize() throws Exception
@ParameterizedTest
@MethodSource("multipartModes")
public void testMaxRequestSize(MultiPartCompliance multiPartCompliance) throws Exception
{
startServer(multiPartCompliance);
OutputStreamRequestContent content = new OutputStreamRequestContent();
MultiPartRequestContent multiPart = new MultiPartRequestContent();
multiPart.addPart(new MultiPart.ContentSourcePart("param", null, null, content));
@ -301,9 +601,12 @@ public class MultiPartServletTest
checkbody.accept(responseContent);
}
@Test
public void testTempFilesDeletedOnError() throws Exception
@ParameterizedTest
@MethodSource("multipartModes")
public void testTempFilesDeletedOnError(MultiPartCompliance multiPartCompliance) throws Exception
{
startServer(multiPartCompliance);
byte[] byteArray = new byte[LARGE_MESSAGE_SIZE];
Arrays.fill(byteArray, (byte)1);
BytesRequestContent content = new BytesRequestContent(byteArray);
@ -320,7 +623,7 @@ public class MultiPartServletTest
.body(multiPart)
.send();
assertEquals(500, response.getStatus());
assertEquals(400, response.getStatus());
assertThat(response.getContentAsString(),
containsString("Multipart Mime part largePart exceeds max filesize"));
}
@ -333,6 +636,8 @@ public class MultiPartServletTest
@Test
public void testMultiPartGzip() throws Exception
{
startServer(MultiPartCompliance.RFC7578);
String contentString = "the quick brown fox jumps over the lazy dog, " +
"the quick brown fox jumps over the lazy dog";
StringRequestContent content = new StringRequestContent(contentString);
@ -357,12 +662,14 @@ public class MultiPartServletTest
assertThat(headers.get(HttpHeader.CONTENT_TYPE), startsWith("multipart/form-data"));
assertThat(headers.get(HttpHeader.CONTENT_ENCODING), is("gzip"));
InputStream inputStream = new GZIPInputStream(responseStream.getInputStream());
String contentType = headers.get(HttpHeader.CONTENT_TYPE);
MultiPartFormInputStream mpis = new MultiPartFormInputStream(inputStream, contentType, null, null);
List<Part> parts = new ArrayList<>(mpis.getParts());
assertThat(parts.size(), is(1));
assertThat(IO.toString(parts.get(0).getInputStream()), is(contentString));
try (InputStream inputStream = new GZIPInputStream(responseStream.getInputStream()))
{
String contentType = headers.get(HttpHeader.CONTENT_TYPE);
MultiPartFormInputStream mpis = new MultiPartFormInputStream(inputStream, contentType, null, null);
List<Part> parts = new ArrayList<>(mpis.getParts());
assertThat(parts.size(), is(1));
assertThat(IO.toString(parts.get(0).getInputStream()), is(contentString));
}
}
}
}

View File

@ -435,6 +435,72 @@ public class CreationTest
}
}
/**
* Test creating a session in a request and then invalidating it in another request
* @throws Exception
*/
@Test
public void testSessionSimpleCreateInvalidate() throws Exception
{
String contextPath = "";
String servletMapping = "/server";
int inactivePeriod = -1; //immortal session
int scavengePeriod = 3;
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT);
cacheFactory.setSaveOnCreate(true);
cacheFactory.setFlushOnResponseCommit(true); //ensure session saved before response returned
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
SessionTestSupport server1 = new SessionTestSupport(0, inactivePeriod, scavengePeriod, cacheFactory, storeFactory);
TestServlet servlet = new TestServlet();
ServletHolder holder = new ServletHolder(servlet);
ServletContextHandler contextHandler = server1.addContext(contextPath);
contextHandler.addServlet(holder, servletMapping);
servlet.setStore(contextHandler.getSessionHandler().getSessionManager().getSessionCache().getSessionDataStore());
server1.start();
int port1 = server1.getPort();
try (StacklessLogging stackless = new StacklessLogging(CreationTest.class.getPackage()))
{
HttpClient client = new HttpClient();
client.start();
String url = "http://localhost:" + port1 + contextPath + servletMapping + "?action=create";
//make a request to set up a session on the server
ContentResponse response = client.GET(url);
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
//Ensure session handling is finished
Awaitility.waitAtMost(5, TimeUnit.SECONDS).until(() -> contextHandler.getSessionHandler().getSessionManager().getSessionCache().getSessionDataStore().exists(servlet._id));
//make a request to re-obtain the session on the server
Request request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=test");
response = request.send();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
Awaitility.waitAtMost(5, TimeUnit.SECONDS).until(() -> contextHandler.getSessionHandler().getSessionManager().getSessionCache().getSessionDataStore().exists(servlet._id));
//at this point the last accessed time should be the creation time
long lastAccessedTime = servlet._lastAccessedTime;
assertEquals(lastAccessedTime, servlet._creationTime);
//make another request to re-obtain the session on the server
request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=test");
response = request.send();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
Awaitility.waitAtMost(5, TimeUnit.SECONDS).until(() -> contextHandler.getSessionHandler().getSessionManager().getSessionCache().getSessionDataStore().exists(servlet._id));
//check that the lastAccessedTime is being updated
assertTrue(lastAccessedTime < servlet._lastAccessedTime);
//make a request to invalidate it
request = client.newRequest("http://localhost:" + port1 + contextPath + servletMapping + "?action=invalidate");
response = request.send();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
}
finally
{
server1.stop();
}
}
public static class MySessionListener implements HttpSessionListener
{
@Override
@ -454,6 +520,8 @@ public class CreationTest
private static final long serialVersionUID = 1L;
public String _id = null;
public SessionDataStore _store;
public long _lastAccessedTime;
public long _creationTime;
public void setStore(SessionDataStore store)
{
@ -469,6 +537,7 @@ public class CreationTest
case "forward" ->
{
HttpSession session = createAndSaveSessionId(request);
assertTrue(session.isNew());
ServletContext contextB = getServletContext().getContext("/contextB");
RequestDispatcher dispatcherB = contextB.getRequestDispatcher(request.getServletPath());
@ -482,6 +551,7 @@ public class CreationTest
case "forwardinv" ->
{
HttpSession session = createAndSaveSessionId(request);
assertTrue(session.isNew());
ServletContext contextB = getServletContext().getContext("/contextB");
RequestDispatcher dispatcherB = contextB.getRequestDispatcher(request.getServletPath());
@ -491,23 +561,38 @@ public class CreationTest
case "forwardc" ->
{
HttpSession session = createAndSaveSessionId(request);
//forward to contextC
ServletContext contextC = getServletContext().getContext("/contextC");
RequestDispatcher dispatcherC = contextC.getRequestDispatcher(request.getServletPath());
dispatcherC.forward(request, httpServletResponse);
assertTrue(session.isNew());
//forward to contextC
ServletContext contextC = getServletContext().getContext("/contextC");
RequestDispatcher dispatcherC = contextC.getRequestDispatcher(request.getServletPath());
dispatcherC.forward(request, httpServletResponse);
}
case "test" ->
{
HttpSession session = request.getSession(false);
assertNotNull(session);
_id = session.getId();
_lastAccessedTime = session.getLastAccessedTime();
assertFalse(session.isNew());
assertNotNull(session.getAttribute("value")); //check we see the session we created earlier
assertNull(session.getAttribute("B")); //check we don't see stuff from other contexts
assertNull(session.getAttribute("C"));
}
case "invalidate" ->
{
HttpSession session = request.getSession(false);
assertNotNull(session);
_id = session.getId();
assertFalse(session.isNew());
session.invalidate();
assertNull(request.getSession(false));
}
case "create", "createinv", "createinvcreate" ->
{
currentRequest.set(request);
HttpSession session = createAndSaveSessionId(request);
assertTrue(session.isNew());
_creationTime = session.getCreationTime();
String check = request.getParameter("check");
if (!StringUtil.isBlank(check) && _store != null)
{

View File

@ -1171,6 +1171,31 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
return getCoreContextHandler().getTempDirectory();
}
protected void makeTempDirectory() throws Exception
{
getCoreContextHandler().makeTempDirectory();
}
protected String getCanonicalNameForTmpDir()
{
return getCoreContextHandler().getCanonicalNameForTmpDir();
}
protected Resource getResourceForTempDirName()
{
Resource resource = getCoreContextHandler().getResourceForTempDirName();
if (resource == null)
{
if (getWar() == null || getWar().length() == 0)
throw new IllegalStateException("No resourceBase or war set for context");
// Use name of given resource in the temporary dirname
resource = getResourceFactory().newResource(getWar());
}
return resource;
}
/**
* If true the temp directory for this
* webapp will be kept when the webapp stops. Otherwise,

View File

@ -38,7 +38,6 @@ public class WebInfConfiguration extends AbstractConfiguration
{
private static final Logger LOG = LoggerFactory.getLogger(WebInfConfiguration.class);
public static final String TEMPDIR_CONFIGURED = "org.eclipse.jetty.tmpdirConfigured";
public static final String TEMPORARY_RESOURCE_BASE = "org.eclipse.jetty.webapp.tmpResourceBase";
protected Resource _preUnpackBaseResource;
@ -83,10 +82,6 @@ public class WebInfConfiguration extends AbstractConfiguration
@Override
public void deconfigure(WebAppContext context) throws Exception
{
//if it wasn't explicitly configured by the user, then unset it
if (!(context.getAttribute(TEMPDIR_CONFIGURED) instanceof Boolean tmpdirConfigured && tmpdirConfigured))
context.setTempDirectory(null);
//reset the base resource back to what it was before we did any unpacking of resources
context.setBaseResource(_preUnpackBaseResource);
}
@ -135,7 +130,6 @@ public class WebInfConfiguration extends AbstractConfiguration
File tempDirectory = context.getTempDirectory();
if (tempDirectory != null)
{
context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE); //the tmp dir was set explicitly
return;
}
@ -150,37 +144,14 @@ public class WebInfConfiguration extends AbstractConfiguration
return;
}
makeTempDirectory(context.getServer().getContext().getTempDirectory(), context);
context.makeTempDirectory();
}
@Deprecated(forRemoval = true, since = "12.0.12")
public void makeTempDirectory(File parent, WebAppContext context)
throws Exception
{
if (parent == null || !parent.exists() || !parent.canWrite() || !parent.isDirectory())
throw new IllegalStateException("Parent for temp dir not configured correctly: " + (parent == null ? "null" : "writeable=" + parent.canWrite()));
boolean persistent = context.isPersistTempDirectory() || "work".equals(parent.toPath().getFileName().toString());
//Create a name for the webapp
String temp = getCanonicalNameForWebAppTmpDir(context);
File tmpDir;
if (persistent)
{
//if it is to be persisted, make sure it will be the same name
//by not using File.createTempFile, which appends random digits
tmpDir = new File(parent, temp);
}
else
{
// ensure dir will always be unique by having classlib generate random path name
tmpDir = Files.createTempDirectory(parent.toPath(), temp).toFile();
tmpDir.deleteOnExit();
}
if (LOG.isDebugEnabled())
LOG.debug("Set temp dir {}", tmpDir);
context.setTempDirectory(tmpDir);
context.setPersistTempDirectory(persistent);
context.makeTempDirectory();
}
public void unpack(WebAppContext context) throws IOException
@ -397,94 +368,10 @@ public class WebInfConfiguration extends AbstractConfiguration
*/
public static String getCanonicalNameForWebAppTmpDir(WebAppContext context)
{
StringBuffer canonicalName = new StringBuffer();
canonicalName.append("jetty-");
//get the host and the port from the first connector
Server server = context.getServer();
if (server != null)
{
Connector[] connectors = context.getServer().getConnectors();
if (connectors.length > 0)
{
//Get the host
String host = null;
int port = 0;
if (connectors != null && (connectors[0] instanceof NetworkConnector))
{
NetworkConnector connector = (NetworkConnector)connectors[0];
host = connector.getHost();
port = connector.getLocalPort();
if (port < 0)
port = connector.getPort();
}
if (host == null)
host = "0.0.0.0";
canonicalName.append(host);
//Get the port
canonicalName.append("-");
//if not available (eg no connectors or connector not started),
//try getting one that was configured.
canonicalName.append(port);
canonicalName.append("-");
}
}
// Resource base
try
{
Resource resource = context.getBaseResource();
if (resource == null)
{
if (context.getWar() == null || context.getWar().length() == 0)
throw new IllegalStateException("No resourceBase or war set for context");
// Set dir or WAR to resource
resource = context.newResource(context.getWar());
}
String resourceBaseName = getResourceBaseName(resource);
canonicalName.append(resourceBaseName);
canonicalName.append("-");
}
catch (Exception e)
{
if (LOG.isDebugEnabled())
LOG.debug("Can't get resource base name", e);
canonicalName.append("-"); // empty resourceBaseName segment
}
//Context name
String contextPath = context.getContextPath();
contextPath = contextPath.replace('/', '_');
contextPath = contextPath.replace('\\', '_');
canonicalName.append(contextPath);
//Virtual host (if there is one)
canonicalName.append("-");
String[] vhosts = context.getVirtualHosts();
if (vhosts == null || vhosts.length <= 0)
canonicalName.append("any");
else
canonicalName.append(vhosts[0]);
// sanitize
for (int i = 0; i < canonicalName.length(); i++)
{
char c = canonicalName.charAt(i);
if (!Character.isJavaIdentifierPart(c) && "-.".indexOf(c) < 0)
canonicalName.setCharAt(i, '.');
}
canonicalName.append("-");
return StringUtil.sanitizeFileSystemName(canonicalName.toString());
return context.getCanonicalNameForTmpDir();
}
@Deprecated(forRemoval = true, since = "12.0.12")
protected static String getResourceBaseName(Resource resource)
{
// Use File System and File interface if present

View File

@ -14,15 +14,22 @@
package org.eclipse.jetty.ee9.webapp;
import java.io.File;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import jakarta.servlet.ServletContext;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.PathMatchers;
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.resource.FileSystemPool;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -34,6 +41,7 @@ import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(WorkDirExtension.class)
@ -139,4 +147,35 @@ public class TempDirTest
webInfConfiguration.resolveTempDirectory(webAppContext);
assertThat(webAppContext.getTempDirectory().getParentFile().toPath(), PathMatchers.isSame(workDir));
}
@Test
public void testTempDirDeleted(WorkDir workDir) throws Exception
{
// Create war on the fly
Path testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
Path warFile = workDir.getEmptyPathDir().resolve("test.war");
Map<String, String> env = new HashMap<>();
env.put("create", "true");
URI uri = URI.create("jar:" + warFile.toUri().toASCIIString());
// Use ZipFS so that we can create paths that are just "/"
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env))
{
Path root = zipfs.getPath("/");
IO.copyDir(testWebappDir, root);
}
//Let jetty create the tmp dir on the fly
Server server = new Server();
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/");
webAppContext.setWarResource(webAppContext.getResourceFactory().newResource(warFile));
server.setHandler(webAppContext);
server.start();
File tempDirectory = webAppContext.getTempDirectory();
server.stop();
assertNull(webAppContext.getTempDirectory());
assertThat("Temp dir exists", !Files.exists(tempDirectory.toPath()));
}
}

View File

@ -593,6 +593,18 @@ case "$ACTION" in
testFileSystemPermissions
if running $JETTY_PID
then
echo "Already Running $(cat $JETTY_PID)!"
exit 1
fi
# remove any lingering state file
if [ -f $JETTY_STATE ]
then
rm $JETTY_STATE
fi
echo -n "Starting Jetty: "
# Startup from a service file
@ -616,13 +628,6 @@ case "$ACTION" in
--
(( DEBUG )) && echo "Starting: start-stop-daemon"
else
if running $JETTY_PID
then
echo "Already Running $(cat $JETTY_PID)!"
exit 1
fi
# Startup if switching users (not as a service, or from root)
if [ -n "$JETTY_USER" ] && [ `whoami` != "$JETTY_USER" ]
then

51
jetty-p2/pom.xml Normal file
View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-project</artifactId>
<version>12.0.12-SNAPSHOT</version>
</parent>
<artifactId>jetty-p2</artifactId>
<packaging>pom</packaging>
<name>Jetty :: P2</name>
<description>Generates a (maven based) P2 Updatesite</description>
<properties>
<enforcer.skip>true</enforcer.skip>
<tycho-version>4.0.8</tycho-version>
</properties>
<dependencies>
<!-- This dependency is to make sure this projects is build after all relevant
artifacts are created -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-home</artifactId>
<version>${project.version}</version>
<type>pom</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-p2-repository-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>maven-p2-site</id>
<goals>
<goal>assemble-maven-repository</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<categoryName>Jetty Bundles</categoryName>
<includeReactor>true</includeReactor>
<includeDependencies>false</includeDependencies>
<includePGPSignature>true</includePGPSignature>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -8,7 +8,7 @@
<packaging>pom</packaging>
<name>Jetty :: Project</name>
<description>The Eclipse Jetty Project</description>
<url>https://eclipse.dev/jetty</url>
<url>https://jetty.org</url>
<inceptionYear>1995</inceptionYear>
<organization>
@ -131,6 +131,7 @@
<module>tests</module>
<module>javadoc</module>
<module>documentation</module>
<module>jetty-p2</module>
</modules>
<scm>