Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-SignInWithEthereum
This commit is contained in:
commit
9581ef15af
|
@ -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:*"
|
||||
|
|
|
@ -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'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(""));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 '%A?'"),
|
||||
Arguments.of("name%", "java.lang.IllegalArgumentException: Not valid encoding '%??'"),
|
||||
Arguments.of("name%A", "java.lang.IllegalArgumentException: Not valid encoding '%A?'"),
|
||||
Arguments.of("name%A&", "java.lang.IllegalArgumentException: Not valid encoding '%A&'"),
|
||||
Arguments.of("name=%", "java.lang.IllegalArgumentException: Not valid encoding '%??'"),
|
||||
Arguments.of("name=A%%A", "java.lang.IllegalArgumentException: Not valid encoding '%%A'"),
|
||||
Arguments.of("name=A%%3D", "java.lang.IllegalArgumentException: Not valid encoding '%%3'"),
|
||||
Arguments.of("%=", "java.lang.IllegalArgumentException: Not valid encoding '%=?'"),
|
||||
Arguments.of("name=%A", "java.lang.IllegalArgumentException: Not valid encoding '%A?'"),
|
||||
Arguments.of("name=value%A", "ava.lang.IllegalArgumentException: Not valid encoding '%A?'"),
|
||||
Arguments.of("n%AH=v", "java.lang.IllegalArgumentException: Not valid encoding '%AH'"),
|
||||
Arguments.of("n=v%AH", "java.lang.IllegalArgumentException: Not valid encoding '%AH'")
|
||||
);
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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='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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
3
pom.xml
3
pom.xml
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue