diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/FormDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/FormDocs.java
new file mode 100644
index 000000000000..fd7d79900556
--- /dev/null
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/security/FormDocs.java
@@ -0,0 +1,49 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+package org.eclipse.jetty.docs.programming.security;
+
+import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
+import org.eclipse.jetty.server.FormFields;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.Fields;
+
+public class FormDocs
+{
+ public void limitFormContent()
+ {
+ ServletContextHandler servletContextHandler = new ServletContextHandler();
+ // tag::limitFormContent[]
+ int maxFormKeys = 100;
+ int maxFormSizeInBytes = 1024;
+ servletContextHandler.setMaxFormContentSize(maxFormSizeInBytes);
+ servletContextHandler.setMaxFormKeys(maxFormKeys);
+ // end::limitFormContent[]
+ }
+
+ public void jettyCoreAPI()
+ {
+ Request request = null;
+ // tag::jettyCoreAPI[]
+ int maxFormKeys = 100;
+ int maxFormSizeInBytes = 1024;
+ Fields fields;
+
+ // Explicit set the form limits.
+ fields = FormFields.getFields(request, maxFormKeys, maxFormSizeInBytes);
+
+ // Rely on default form limits.
+ fields = FormFields.getFields(request);
+ // end::jettyCoreAPI[]
+ }
+}
diff --git a/documentation/jetty/modules/operations-guide/nav.adoc b/documentation/jetty/modules/operations-guide/nav.adoc
index eb03e27de1f4..85a995bb712f 100644
--- a/documentation/jetty/modules/operations-guide/nav.adoc
+++ b/documentation/jetty/modules/operations-guide/nav.adoc
@@ -32,6 +32,8 @@
* xref:jstl/index.adoc[]
* xref:jsf-taglibs/index.adoc[]
* xref:jndi/index.adoc[]
+* Jetty Security
+** xref:security/configuring-form-size.adoc[]
* xref:jaas/index.adoc[]
* xref:jaspi/index.adoc[]
* xref:jmx/index.adoc[]
diff --git a/documentation/jetty/modules/operations-guide/pages/security/configuring-form-size.adoc b/documentation/jetty/modules/operations-guide/pages/security/configuring-form-size.adoc
new file mode 100644
index 000000000000..451965f5b9a0
--- /dev/null
+++ b/documentation/jetty/modules/operations-guide/pages/security/configuring-form-size.adoc
@@ -0,0 +1,38 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+[[limit-form-content]]
+= Limiting Form Content
+
+Forms can be a vector for denial-of-service attacks, like explained in xref:programming-guide:security/configuring-form-size.adoc[this section] of the Programming Guide.
+
+== Configuring Form Limits for a Web Application
+
+To configure the form limits for a single web application, the `WebAppContext` instance can be configured from a context XML file or `WEB-INF/jetty-web.xml` file:
+
+[,xml,subs=attributes+]
+----
+
+
+ ...
+
+ 200000
+ 200
+
+
+----
+
+These settings can also be set via the following `ServletContext` attributes.
+
+- `org.eclipse.jetty.server.Request.maxFormKeys`
+- `org.eclipse.jetty.server.Request.maxFormContentSize`
diff --git a/documentation/jetty/modules/programming-guide/nav.adoc b/documentation/jetty/modules/programming-guide/nav.adoc
index ac50c0d7f133..a7094cf4c958 100644
--- a/documentation/jetty/modules/programming-guide/nav.adoc
+++ b/documentation/jetty/modules/programming-guide/nav.adoc
@@ -43,6 +43,8 @@
** xref:troubleshooting/state-tracking.adoc[]
** xref:troubleshooting/component-dump.adoc[]
** xref:troubleshooting/debugging.adoc[]
+* Jetty Security
+** xref:security/configuring-form-size.adoc[]
* Migration Guides
** xref:migration/94-to-10.adoc[]
** xref:migration/11-to-12.adoc[]
diff --git a/documentation/jetty/modules/programming-guide/pages/security/configuring-form-size.adoc b/documentation/jetty/modules/programming-guide/pages/security/configuring-form-size.adoc
new file mode 100644
index 000000000000..ae0443458119
--- /dev/null
+++ b/documentation/jetty/modules/programming-guide/pages/security/configuring-form-size.adoc
@@ -0,0 +1,56 @@
+//
+// ========================================================================
+// 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
+// ========================================================================
+//
+
+[[limit-form-content]]
+= Limiting Form Content
+
+Form content sent to the server is processed by Jetty into a map of parameters to be used by the web application.
+Forms can be a vector for denial-of-service attacks, since significant memory and CPU can be consumed if a malicious client sends very large form content or a large number of form keys.
+Thus, Jetty limits the amount of data and keys that can be in a form posted to Jetty.
+
+The default maximum size Jetty permits is 200000 bytes and 1000 keys.
+You can change this default for a particular web application or for all web applications on a particular `Server` instance.
+
+== Configuring Form Limits for a Web Application
+
+To configure the form limits for a single web application, the `ServletContextHandler` (or `WebAppContext`) instance can be configured using the following methods:
+
+[,java,indent=0]
+----
+include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/FormDocs.java[tags=limitFormContent]
+----
+
+These settings can also be set via the following `ServletContext` attributes.
+
+- `org.eclipse.jetty.server.Request.maxFormKeys`
+- `org.eclipse.jetty.server.Request.maxFormContentSize`
+
+== Configuring Default Form Limits for the Server
+
+The following system properties can be used to configure form limits for the entire server, including all contexts without explicit configuration:
+
+- `org.eclipse.jetty.server.Request.maxFormKeys`
+- `org.eclipse.jetty.server.Request.maxFormContentSize`.
+
+If not configured for either the server or a specific context, then the default `maxFormKeys` is 1000 and the default `maxFormContentSize` is 200000.
+
+== Limiting Form Content with Jetty Core API
+
+The class `FormFields` is used to parse forms with the Jetty Core API, which provides `onFields` and `getFields` static methods to provide both async & blocking ways to parse a form.
+
+These methods can take parameters for `maxFields` and `maxLength` which can be used to limit the form content.
+
+[,java,indent=0]
+----
+include::code:example$src/main/java/org/eclipse/jetty/docs/programming/security/FormDocs.java[tags=jettyCoreAPI]
+----
\ No newline at end of file
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FormFields.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FormFields.java
index 1bf6997c1453..a656401b2ca1 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FormFields.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FormFields.java
@@ -130,8 +130,8 @@ public static Fields getFields(Request request)
* Calls to {@code onFields} and {@code getFields} methods are idempotent, and
* can be called multiple times, with subsequent calls returning the results of the first call.
* @param request The request to get or read the Fields from
- * @param maxFields The maximum number of fields to accept
- * @param maxLength The maximum length of fields
+ * @param maxFields The maximum number of fields to accept or -1 for unlimited
+ * @param maxLength The maximum length of fields or -1 for unlimited.
* @return the Fields
* @see #onFields(Request, Promise.Invocable)
* @see #onFields(Request, Charset, Promise.Invocable)
@@ -185,8 +185,8 @@ public static void onFields(Request request, Charset charset, Promise.Invocable<
*
* @param request The request to get or read the Fields from
* @param charset The {@link Charset} of the request content, if previously extracted.
- * @param maxFields The maximum number of fields to accept
- * @param maxLength The maximum length of fields
+ * @param maxFields The maximum number of fields to accept or -1 for unlimited
+ * @param maxLength The maximum length of fields or -1 for unlimited
* @param promise The action to take when the FormFields are available.
*/
public static void onFields(Request request, Charset charset, int maxFields, int maxLength, Promise.Invocable promise)
@@ -249,8 +249,8 @@ public static CompletableFuture from(Request request, Charset charset)
* @param request The {@link Request} in which to look for an existing {@link FormFields} attribute,
* using the classname as the attribute name, else the request is used
* as a {@link Content.Source} from which to read the fields and set the attribute.
- * @param maxFields The maximum number of fields to be parsed
- * @param maxLength The maximum total size of the fields
+ * @param maxFields The maximum number of fields to be parsed or -1 for unlimited
+ * @param maxLength The maximum total size of the fields or -1 for unlimited
* @return A {@link CompletableFuture} that will provide the {@link Fields} or a failure.
* @deprecated use {@link #onFields(Request, Charset, int, int, Promise.Invocable)} instead.
*/
@@ -266,8 +266,8 @@ public static CompletableFuture from(Request request, int maxFields, int
* using the classname as the attribute name, else the request is used
* as a {@link Content.Source} from which to read the fields and set the attribute.
* @param charset the {@link Charset} to use for byte to string conversion.
- * @param maxFields The maximum number of fields to be parsed
- * @param maxLength The maximum total size of the fields
+ * @param maxFields The maximum number of fields to be parsed or -1 for unlimited
+ * @param maxLength The maximum total size of the fields or -1 for unlimited
* @return A {@link CompletableFuture} that will provide the {@link Fields} or a failure.
* @deprecated use {@link #onFields(Request, Charset, int, int, Promise.Invocable)} instead.
*/
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java
index 4b2a34fa8b22..32374f755eff 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java
@@ -322,6 +322,8 @@ public void dump(Appendable out, String indent) throws IOException
new ClassLoaderDump(getClassLoader()),
Dumpable.named("context " + this, getContext()),
Dumpable.named("handler attributes " + this, getContext().getPersistentAttributes()),
+ Dumpable.named("maxFormKeys ", getMaxFormKeys()),
+ Dumpable.named("maxFormContentSize ", getMaxFormContentSize()),
new DumpableCollection("initparams " + this, getInitParams().entrySet()));
}
@@ -2045,6 +2047,53 @@ public void setExtendedListenerTypes(boolean b)
{
_servletContext.setExtendedListenerTypes(b);
}
+
+ @Override
+ public Object getAttribute(String name)
+ {
+ return switch (name)
+ {
+ case FormFields.MAX_FIELDS_ATTRIBUTE -> getMaxFormKeys();
+ case FormFields.MAX_LENGTH_ATTRIBUTE -> getMaxFormContentSize();
+ default -> super.getAttribute(name);
+ };
+ }
+
+ @Override
+ public Object setAttribute(String name, Object attribute)
+ {
+ return switch (name)
+ {
+ case FormFields.MAX_FIELDS_ATTRIBUTE ->
+ {
+ int oldValue = getMaxFormKeys();
+ if (attribute == null)
+ setMaxFormKeys(DEFAULT_MAX_FORM_KEYS);
+ else
+ setMaxFormKeys(Integer.parseInt(attribute.toString()));
+ yield oldValue;
+ }
+ case FormFields.MAX_LENGTH_ATTRIBUTE ->
+ {
+ int oldValue = getMaxFormContentSize();
+ if (attribute == null)
+ setMaxFormContentSize(DEFAULT_MAX_FORM_CONTENT_SIZE);
+ else
+ setMaxFormContentSize(Integer.parseInt(attribute.toString()));
+ yield oldValue;
+ }
+ default -> super.setAttribute(name, attribute);
+ };
+ }
+
+ @Override
+ public Set getAttributeNameSet()
+ {
+ Set names = new HashSet<>(super.getAttributeNameSet());
+ names.add(FormFields.MAX_FIELDS_ATTRIBUTE);
+ names.add(FormFields.MAX_LENGTH_ATTRIBUTE);
+ return Collections.unmodifiableSet(names);
+ }
}
public class ServletContextApi implements jakarta.servlet.ServletContext
diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java
index 353a410bcc77..efeea31e8faf 100644
--- a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java
+++ b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java
@@ -57,6 +57,7 @@
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ClassLoaderDump;
+import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
@@ -987,12 +988,15 @@ else if (getBaseResource() != null)
name = String.format("%s@%x", name, hashCode());
dumpObjects(out, indent,
+ Dumpable.named("environment", ServletContextHandler.ENVIRONMENT.getName()),
new ClassLoaderDump(getClassLoader()),
new DumpableCollection("Systemclasses " + name, systemClasses),
new DumpableCollection("Serverclasses " + name, serverClasses),
new DumpableCollection("Configurations " + name, _configurations),
new DumpableCollection("Handler attributes " + name, asAttributeMap().entrySet()),
new DumpableCollection("Context attributes " + name, getContext().asAttributeMap().entrySet()),
+ Dumpable.named("maxFormKeys ", getMaxFormKeys()),
+ Dumpable.named("maxFormContentSize ", getMaxFormContentSize()),
new DumpableCollection("EventListeners " + this, getEventListeners()),
new DumpableCollection("Initparams " + name, getInitParams().entrySet())
);
diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
index b85734a7a1ee..1b7693f926c6 100644
--- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
+++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
@@ -94,6 +94,7 @@
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.component.Environment;
import org.eclipse.jetty.util.component.LifeCycle;
@@ -302,7 +303,10 @@ public void insertHandler(org.eclipse.jetty.server.Handler.Singleton coreHandler
@Override
public void dump(Appendable out, String indent) throws IOException
{
- dumpObjects(out, indent, new DumpableCollection("initparams " + this, getInitParams().entrySet()));
+ dumpObjects(out, indent,
+ Dumpable.named("maxFormKeys ", getMaxFormKeys()),
+ Dumpable.named("maxFormContentSize ", getMaxFormContentSize()),
+ new DumpableCollection("initparams " + this, getInitParams().entrySet()));
}
public APIContext getServletContext()
@@ -2861,6 +2865,53 @@ public APIContext getAPIContext()
{
return _apiContext;
}
+
+ @Override
+ public Object getAttribute(String name)
+ {
+ return switch (name)
+ {
+ case FormFields.MAX_FIELDS_ATTRIBUTE -> getMaxFormKeys();
+ case FormFields.MAX_LENGTH_ATTRIBUTE -> getMaxFormContentSize();
+ default -> super.getAttribute(name);
+ };
+ }
+
+ @Override
+ public Object setAttribute(String name, Object attribute)
+ {
+ return switch (name)
+ {
+ case FormFields.MAX_FIELDS_ATTRIBUTE ->
+ {
+ int oldValue = getMaxFormKeys();
+ if (attribute == null)
+ setMaxFormKeys(DEFAULT_MAX_FORM_KEYS);
+ else
+ setMaxFormKeys(Integer.parseInt(attribute.toString()));
+ yield oldValue;
+ }
+ case FormFields.MAX_LENGTH_ATTRIBUTE ->
+ {
+ int oldValue = getMaxFormContentSize();
+ if (attribute == null)
+ setMaxFormContentSize(DEFAULT_MAX_FORM_CONTENT_SIZE);
+ else
+ setMaxFormContentSize(Integer.parseInt(attribute.toString()));
+ yield oldValue;
+ }
+ default -> super.setAttribute(name, attribute);
+ };
+ }
+ }
+
+ @Override
+ public Set getAttributeNameSet()
+ {
+ Set names = new HashSet<>(super.getAttributeNameSet());
+ names.add(FormFields.MAX_FIELDS_ATTRIBUTE);
+ names.add(FormFields.MAX_LENGTH_ATTRIBUTE);
+ return Collections.unmodifiableSet(names);
}
private class CoreToNestedHandler extends Abstract
diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java
index 7ccf48b9e25d..eb028398e2f1 100644
--- a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java
+++ b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java
@@ -945,6 +945,8 @@ else if (getResourceBase() != null)
dumpObjects(out, indent,
Dumpable.named("environment", ContextHandler.ENVIRONMENT.getName()),
+ Dumpable.named("maxFormKeys ", getMaxFormKeys()),
+ Dumpable.named("maxFormContentSize ", getMaxFormContentSize()),
new ClassLoaderDump(getClassLoader()),
new DumpableCollection("Systemclasses " + name, systemClasses),
new DumpableCollection("Serverclasses " + name, serverClasses),