From c658ae705618db0ba220856d510c13ab7c8a9495 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 17 Mar 2020 19:18:39 +1100 Subject: [PATCH 1/2] Issue #4673 - fix MultiPart parsing for content split in boundary Signed-off-by: Lachlan Roberts --- .../http/MultiPartFormInputStreamTest.java | 95 +++++++++++++++++++ .../org/eclipse/jetty/util/SearchPattern.java | 28 ++---- .../eclipse/jetty/util/SearchPatternTest.java | 85 ++++++++++------- 3 files changed, 158 insertions(+), 50 deletions(-) diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartFormInputStreamTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartFormInputStreamTest.java index 48beb58d3a1..f9ba6bb0b76 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartFormInputStreamTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/MultiPartFormInputStreamTest.java @@ -23,6 +23,8 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; @@ -33,6 +35,8 @@ import javax.servlet.http.Part; import org.eclipse.jetty.http.MultiPartFormInputStream.MultiPart; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.IO; import org.junit.jupiter.api.Test; @@ -885,6 +889,97 @@ public class MultiPartFormInputStreamTest assertEquals("the end", baos.toString("US-ASCII")); } + @Test + public void testFragmentation() throws IOException + { + String contentType = "multipart/form-data, boundary=----WebKitFormBoundaryhXfFAMfUnUKhmqT8"; + String payload1 = + "------WebKitFormBoundaryhXfFAMfUnUKhmqT8\r\n" + + "Content-Disposition: form-data; name=\"field1\"\r\n\r\n" + + "value1" + + "\r\n--"; + String payload2 = "----WebKitFormBoundaryhXfFAMfUnUKhmqT8\r\n" + + "Content-Disposition: form-data; name=\"field2\"\r\n\r\n" + + "value2" + + "\r\n------WebKitFormBoundaryhXfFAMfUnUKhmqT8--\r\n"; + + // Split the content into separate reads, with the content broken up on the boundary string. + AppendableInputStream stream = new AppendableInputStream(); + stream.append(payload1); + stream.append(""); + stream.append(payload2); + stream.endOfContent(); + + MultipartConfigElement config = new MultipartConfigElement(_dirname); + MultiPartFormInputStream mpis = new MultiPartFormInputStream(stream, contentType, config, _tmpDir); + mpis.setDeleteOnExit(true); + + // Check size. + Collection parts = mpis.getParts(); + assertThat(parts.size(), is(2)); + + // Check part content. + assertThat(IO.toString(mpis.getPart("field1").getInputStream()), is("value1")); + assertThat(IO.toString(mpis.getPart("field2").getInputStream()), is("value2")); + } + + static class AppendableInputStream extends InputStream + { + private static final ByteBuffer EOF = ByteBuffer.allocate(0); + private final BlockingArrayQueue buffers = new BlockingArrayQueue<>(); + private ByteBuffer current; + + public void append(String data) + { + append(data.getBytes(StandardCharsets.US_ASCII)); + } + + public void append(byte[] data) + { + buffers.add(BufferUtil.toBuffer(data)); + } + + public void endOfContent() + { + buffers.add(EOF); + } + + @Override + public int read() throws IOException + { + byte[] buf = new byte[1]; + while (true) + { + int len = read(buf, 0, 1); + if (len < 0) + return -1; + if (len > 0) + return buf[0]; + } + } + + @Override + public int read(byte[] b, int off, int len) throws IOException + { + if (current == null) + current = buffers.poll(); + if (current == EOF) + return -1; + if (BufferUtil.isEmpty(current)) + { + current = null; + return 0; + } + + ByteBuffer buffer = ByteBuffer.wrap(b, off, len); + buffer.flip(); + int read = BufferUtil.append(buffer, current); + if (BufferUtil.isEmpty(current)) + current = buffers.poll(); + return read; + } + } + @Test public void testQuotedPrintableEncoding() throws Exception { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/SearchPattern.java b/jetty-util/src/main/java/org/eclipse/jetty/util/SearchPattern.java index 0540431e723..a515acbe38f 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/SearchPattern.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/SearchPattern.java @@ -36,9 +36,9 @@ import java.util.Arrays; */ public class SearchPattern { - static final int alphabetSize = 256; - private int[] table; - private byte[] pattern; + private static final int ALPHABET_SIZE = 256; + private final int[] table = new int[ALPHABET_SIZE]; + private final byte[] pattern; /** * Produces a SearchPattern instance which can be used @@ -70,16 +70,11 @@ public class SearchPattern private SearchPattern(byte[] pattern) { this.pattern = pattern; - if (pattern.length == 0) throw new IllegalArgumentException("Empty Pattern"); //Build up the pre-processed table for this pattern. - table = new int[alphabetSize]; - for (int i = 0; i < table.length; ++i) - { - table[i] = pattern.length; - } + Arrays.fill(table, pattern.length); for (int i = 0; i < pattern.length - 1; ++i) { table[0xff & pattern[i]] = pattern.length - 1 - i; @@ -97,7 +92,7 @@ public class SearchPattern */ public int match(byte[] data, int offset, int length) { - validate(data, offset, length); + validateArgs(data, offset, length); int skip = offset; while (skip <= offset + length - pattern.length) @@ -125,7 +120,7 @@ public class SearchPattern */ public int endsWith(byte[] data, int offset, int length) { - validate(data, offset, length); + validateArgs(data, offset, length); int skip = (pattern.length <= length) ? (offset + length - pattern.length) : offset; while (skip < offset + length) @@ -136,10 +131,8 @@ public class SearchPattern return (offset + length - skip); } - if (skip + pattern.length - 1 < data.length) - skip += table[0xff & data[skip + pattern.length - 1]]; - else - skip++; + // We can't use the pre-processed table as we are not matching on the full pattern. + skip++; } return 0; @@ -157,10 +150,9 @@ public class SearchPattern */ public int startsWith(byte[] data, int offset, int length, int matched) { - validate(data, offset, length); + validateArgs(data, offset, length); int matchedCount = 0; - for (int i = 0; i < pattern.length - matched && i < length; i++) { if (data[offset + i] == pattern[i + matched]) @@ -180,7 +172,7 @@ public class SearchPattern * @param offset The offset within the data to start the search * @param length The length of the data to search */ - private void validate(byte[] data, int offset, int length) + private void validateArgs(byte[] data, int offset, int length) { if (offset < 0) throw new IllegalArgumentException("offset was negative"); diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/SearchPatternTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/SearchPatternTest.java index e29e08e2222..943d963bb3f 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/SearchPatternTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/SearchPatternTest.java @@ -32,10 +32,10 @@ public class SearchPatternTest @Test public void testBasicSearch() { - byte[] p1 = new String("truth").getBytes(StandardCharsets.US_ASCII); - byte[] p2 = new String("evident").getBytes(StandardCharsets.US_ASCII); - byte[] p3 = new String("we").getBytes(StandardCharsets.US_ASCII); - byte[] d = new String("we hold these truths to be self evident").getBytes(StandardCharsets.US_ASCII); + byte[] p1 = "truth".getBytes(StandardCharsets.US_ASCII); + byte[] p2 = "evident".getBytes(StandardCharsets.US_ASCII); + byte[] p3 = "we".getBytes(StandardCharsets.US_ASCII); + byte[] d = "we hold these truths to be self evident".getBytes(StandardCharsets.US_ASCII); // Testing Compiled Pattern p1 "truth" SearchPattern sp1 = SearchPattern.compile(p1); @@ -65,8 +65,8 @@ public class SearchPatternTest @Test public void testDoubleMatch() { - byte[] p = new String("violent").getBytes(StandardCharsets.US_ASCII); - byte[] d = new String("These violent delights have violent ends.").getBytes(StandardCharsets.US_ASCII); + byte[] p = "violent".getBytes(StandardCharsets.US_ASCII); + byte[] d = "These violent delights have violent ends.".getBytes(StandardCharsets.US_ASCII); SearchPattern sp = SearchPattern.compile(p); assertEquals(6, sp.match(d, 0, d.length)); assertEquals(-1, sp.match(d, 6, p.length - 1)); @@ -113,8 +113,8 @@ public class SearchPatternTest @Test public void testAlmostMatch() { - byte[] p = new String("violent").getBytes(StandardCharsets.US_ASCII); - byte[] d = new String("vio lent violen v iolent violin vioviolenlent viiolent").getBytes(StandardCharsets.US_ASCII); + byte[] p = "violent".getBytes(StandardCharsets.US_ASCII); + byte[] d = "vio lent violen v iolent violin vioviolenlent viiolent".getBytes(StandardCharsets.US_ASCII); SearchPattern sp = SearchPattern.compile(p); assertEquals(-1, sp.match(d, 0, d.length)); } @@ -123,14 +123,14 @@ public class SearchPatternTest public void testOddSizedPatterns() { // Test Large Pattern - byte[] p = new String("pneumonoultramicroscopicsilicovolcanoconiosis").getBytes(StandardCharsets.US_ASCII); - byte[] d = new String("pneumon").getBytes(StandardCharsets.US_ASCII); + byte[] p = "pneumonoultramicroscopicsilicovolcanoconiosis".getBytes(StandardCharsets.US_ASCII); + byte[] d = "pneumon".getBytes(StandardCharsets.US_ASCII); SearchPattern sp = SearchPattern.compile(p); assertEquals(-1, sp.match(d, 0, d.length)); // Test Single Character Pattern - p = new String("s").getBytes(StandardCharsets.US_ASCII); - d = new String("the cake is a lie").getBytes(StandardCharsets.US_ASCII); + p = "s".getBytes(StandardCharsets.US_ASCII); + d = "the cake is a lie".getBytes(StandardCharsets.US_ASCII); sp = SearchPattern.compile(p); assertEquals(10, sp.match(d, 0, d.length)); } @@ -138,30 +138,36 @@ public class SearchPatternTest @Test public void testEndsWith() { - byte[] p = new String("pneumonoultramicroscopicsilicovolcanoconiosis").getBytes(StandardCharsets.US_ASCII); - byte[] d = new String("pneumonoultrami").getBytes(StandardCharsets.US_ASCII); + byte[] p = "pneumonoultramicroscopicsilicovolcanoconiosis".getBytes(StandardCharsets.US_ASCII); + byte[] d = "pneumonoultrami".getBytes(StandardCharsets.US_ASCII); SearchPattern sp = SearchPattern.compile(p); assertEquals(15, sp.endsWith(d, 0, d.length)); - p = new String("abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII); - d = new String("abcdefghijklmnopqrstuvwxyzabcdefghijklmno").getBytes(StandardCharsets.US_ASCII); + p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); + d = "abcdefghijklmnopqrstuvwxyzabcdefghijklmno".getBytes(StandardCharsets.US_ASCII); sp = SearchPattern.compile(p); assertEquals(0, sp.match(d, 0, d.length)); assertEquals(-1, sp.match(d, 1, d.length - 1)); assertEquals(15, sp.endsWith(d, 0, d.length)); - p = new String("abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII); - d = new String("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII); + p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); + d = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); sp = SearchPattern.compile(p); assertEquals(0, sp.match(d, 0, d.length)); assertEquals(26, sp.match(d, 1, d.length - 1)); assertEquals(26, sp.endsWith(d, 0, d.length)); //test no match - p = new String("hello world").getBytes(StandardCharsets.US_ASCII); - d = new String("there is definitely no match in here").getBytes(StandardCharsets.US_ASCII); + p = "hello world".getBytes(StandardCharsets.US_ASCII); + d = "there is definitely no match in here".getBytes(StandardCharsets.US_ASCII); sp = SearchPattern.compile(p); assertEquals(0, sp.endsWith(d, 0, d.length)); + + //Test with range on array. + p = "abcde".getBytes(StandardCharsets.US_ASCII); + d = "?abc00000".getBytes(StandardCharsets.US_ASCII); + sp = SearchPattern.compile(p); + assertEquals(3, sp.endsWith(d, 0, 4)); } @Test @@ -178,39 +184,54 @@ public class SearchPatternTest private void testStartsWith(String offset) { - byte[] p = new String("abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII); - byte[] d = new String(offset + "ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII); + byte[] p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); + byte[] d = (offset + "ijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII); SearchPattern sp = SearchPattern.compile(p); assertEquals(18 + offset.length(), sp.match(d, offset.length(), d.length - offset.length())); assertEquals(-1, sp.match(d, offset.length() + 19, d.length - 19 - offset.length())); assertEquals(26, sp.startsWith(d, offset.length(), d.length - offset.length(), 8)); - p = new String("abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII); - d = new String(offset + "ijklmnopqrstuvwxyNOMATCH").getBytes(StandardCharsets.US_ASCII); + p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); + d = (offset + "ijklmnopqrstuvwxyNOMATCH").getBytes(StandardCharsets.US_ASCII); sp = SearchPattern.compile(p); assertEquals(0, sp.startsWith(d, offset.length(), d.length - offset.length(), 8)); - p = new String("abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII); - d = new String(offset + "abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII); + p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); + d = (offset + "abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII); sp = SearchPattern.compile(p); assertEquals(26, sp.startsWith(d, offset.length(), d.length - offset.length(), 0)); //test no match - p = new String("hello world").getBytes(StandardCharsets.US_ASCII); - d = new String(offset + "there is definitely no match in here").getBytes(StandardCharsets.US_ASCII); + p = "hello world".getBytes(StandardCharsets.US_ASCII); + d = (offset + "there is definitely no match in here").getBytes(StandardCharsets.US_ASCII); sp = SearchPattern.compile(p); assertEquals(0, sp.startsWith(d, offset.length(), d.length - offset.length(), 0)); //test large pattern small buffer - p = new String("abcdefghijklmnopqrstuvwxyz").getBytes(StandardCharsets.US_ASCII); - d = new String(offset + "mnopqrs").getBytes(StandardCharsets.US_ASCII); + p = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); + d = (offset + "mnopqrs").getBytes(StandardCharsets.US_ASCII); sp = SearchPattern.compile(p); assertEquals(19, sp.startsWith(d, offset.length(), d.length - offset.length(), 12)); //partial pattern - p = new String("abcdef").getBytes(StandardCharsets.US_ASCII); - d = new String(offset + "cde").getBytes(StandardCharsets.US_ASCII); + p = "abcdef".getBytes(StandardCharsets.US_ASCII); + d = (offset + "cde").getBytes(StandardCharsets.US_ASCII); sp = SearchPattern.compile(p); assertEquals(5, sp.startsWith(d, offset.length(), d.length - offset.length(), 2)); } + + @Test + public void testExampleFrom4673() + { + SearchPattern pattern = SearchPattern.compile("\r\n------WebKitFormBoundaryhXfFAMfUnUKhmqT8".getBytes(StandardCharsets.US_ASCII)); + byte[] data = new byte[]{118,97,108,117,101,49, + '\r','\n','-','-','-','-', + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0}; + int length = 12; + + int partialMatch = pattern.endsWith(data, 0, length); + System.err.println("match1: " + partialMatch); + } } From 34488b71b613909c9e5afc8c7eee8d57289bbfb1 Mon Sep 17 00:00:00 2001 From: Chris Walker Date: Fri, 20 Mar 2020 14:26:13 -0500 Subject: [PATCH 2/2] Updating Weld documentation. Resolves #3935 --- .../asciidoc/development/frameworks/weld.adoc | 118 +++++++++++++++--- 1 file changed, 100 insertions(+), 18 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/development/frameworks/weld.adoc b/jetty-documentation/src/main/asciidoc/development/frameworks/weld.adoc index 5e957e1ea07..bd937ab7874 100644 --- a/jetty-documentation/src/main/asciidoc/development/frameworks/weld.adoc +++ b/jetty-documentation/src/main/asciidoc/development/frameworks/weld.adoc @@ -25,30 +25,73 @@ It is easily configured with Jetty 9. [[weld-setup-distro]] ==== Weld Setup -The easiest way to configure weld is within the jetty distribution itself: +The easiest way to configure weld is within the Jetty distribution itself. +This can be accomplished either by enabling one of the startup link:#startup-modules[modules] for Weld, or by creating/editing a `jetty-web.xml` descriptor (see also https://www.eclipse.org/jetty/documentation/current/jetty-web-xml-config.html[Jetty XML Reference]). -1. Enable the startup link:#startup-modules[module] called "cdi". -2. Ensure your `WEB-INF/web.xml` contains the following: -+ -[source, xml, subs="{sub-order}"] ----- - - org.jboss.weld.environment.servlet.Listener - +===== Jetty Weld Modules - - Object factory for the CDI Bean Manager - BeanManager - javax.enterprise.inject.spi.BeanManager - ----- +====== Jetty `cdi-decorate` Module -That should be it so when you start up your jetty distribution with the webapp you should see output similar to the following (providing your logging is the default configuration): +Since Jetty 9.4.20 and Weld 3.1.2.Final, the Weld/Jetty integration uses the jetty `cdi-decorate` module. +To activate this module in Jetty the `cdi-decorate` module needs activated on the command line, which can be done as follows: + +------------------------- +cd $JETTY_BASE +java -jar $JETTY_HOME/start.jar --add-to-start=cdi-decorate +------------------------- + +====== Jetty `cdi2` Module + +For versions prior to Jetty 9.4.20 and Weld 3.1.2, the Weld/Jetty integration required some internal Jetty APIs to be made visible to the web application. +This can be done using the deprecated `cdi2` module either by activating the `cdi2` module: + +------------------------- +cd $JETTY_BASE +java -jar $JETTY_HOME/start.jar --add-to-start=cdi2 +------------------------- + + +===== Jetty-Web XML + + +[source.XML, xml] +------------------------------------------------------------- + + + + + -org.eclipse.jetty.util.Decorator + + + -org.eclipse.jetty.util.DecoratedObjectFactory + + + -org.eclipse.jetty.server.handler.ContextHandler. + + + -org.eclipse.jetty.server.handler.ContextHandler + + + -org.eclipse.jetty.servlet.ServletContextHandler + + +------------------------------------------------------------- + +____ +[TIP] +Directly modifying the web application classpath via `jetty-web.xml` will not work for Jetty 10.0.0 and later. +____ + +===== Jetty `cdi-spi` Module +Since Jetty 9.4.20 the Jetty `cdi-spi` module has been available that integrates any compliant CDI implementation by directly calling the CDI SPI. +Since Weld support specific Jetty integration, it is not recommended to use this module with Weld. + +When you start up your Jetty distribution with the webapp you should see output similar to the following (providing your logging is the default configuration): [source, screen, subs="{sub-order}"] .... 2015-06-18 12:13:54.924:INFO::main: Logging initialized @485ms -2015-06-18 12:13:55.231:INFO:oejs.Server:main: jetty-9.3.1-SNAPSHOT +2015-06-18 12:13:55.231:INFO:oejs.Server:main: jetty-{VERSION} 2015-06-18 12:13:55.264:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///tmp/cdi-demo/webapps/] at interval 1 2015-06-18 12:13:55.607:WARN:oeja.AnnotationConfiguration:main: ServletContainerInitializers: detected. Class hierarchy: empty Jun 18, 2015 12:13:55 PM org.jboss.weld.environment.servlet.EnhancedListener onStartup @@ -80,4 +123,43 @@ INFO: WELD-ENV-001009: org.jboss.weld.environment.servlet.Listener used for Serv .... -For use with the jetty-maven-plugin, the best idea is to make the org.jboss.weld.servlet:weld-servlet artifact a _plugin_ dependency (__not__ a webapp dependency), then follow step 2 above. +For use with the jetty-maven-plugin, the best idea is to make the org.jboss.weld.servlet:weld-servlet artifact a _plugin_ dependency (__not__ a webapp dependency). + +[[weld-embedded]] +==== Embedded Jetty + +When starting embedded Jetty programmatically from the `main` method it is necessary to register Weld's listener: + +[source.JAVA, java] +---- +public class Main { + + public static void main(String[] args) throws Exception { + Server jetty = new Server(8080); + WebAppContext context = new WebAppContext(); + context.setContextPath("/"); + context.setResourceBase("src/main/resources"); + jetty.setHandler(context); + context.addServlet(HelloWorldServlet.class, "/*"); + + context.addEventListener(new DecoratingListener()); # <1> + context.addEventListener(new Listener()); # <2> + + jetty.start(); + jetty.join(); + } + + public static class HelloWorldServlet extends HttpServlet { + + @Inject BeanManager manager; + + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.setContentType("text/plain"); + resp.getWriter().append("Hello from " + manager); + } + } +} + +<1> Jetty's `org.eclipse.jetty.webapp.DecoratingListener` registered programmatically (since Jetty-9.4.20) +<2> Weld's `org.jboss.weld.environment.servlet.Listener` registered programmatically +----