Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1104,8 +1104,8 @@ public static class Parser
private final Utf8StringBuilder text = new Utf8StringBuilder();
private final String boundary;
private final SearchPattern boundaryFinder;
private final MultiPartCompliance compliance;
private final Listener listener;
private MultiPartCompliance compliance;
private int partHeadersLength;
private int partHeadersMaxLength = -1;
private State state;
Expand Down Expand Up @@ -1160,6 +1160,16 @@ public int getPartHeadersMaxLength()
return partHeadersMaxLength;
}

public MultiPartCompliance getMultiPartCompliance()
{
return compliance;
}

public void setMultiPartCompliance(MultiPartCompliance multiPartCompliance)
{
compliance = Objects.requireNonNull(multiPartCompliance);
}

/**
* @param partHeadersMaxLength the max length of a {@link Part} headers, in bytes, or -1 for unlimited length
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public String getDescription()
public static final MultiPartCompliance LEGACY = new MultiPartCompliance(
"LEGACY", EnumSet.complementOf(EnumSet.of(Violation.BASE64_TRANSFER_ENCODING)));

private static final List<MultiPartCompliance> KNOWN_MODES = Arrays.asList(RFC7578, LEGACY);
private static final List<MultiPartCompliance> KNOWN_MODES = Arrays.asList(RFC7578, RFC7578_STRICT, LEGACY);
private static final AtomicInteger __custom = new AtomicInteger();

public static MultiPartCompliance valueOf(String name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,6 @@ public static class Parser
private final PartsListener listener = new PartsListener();
private final MultiPart.Parser parser;
private ByteBufferPool.Sized bufferPool;
private MultiPartCompliance compliance;
private ComplianceViolation.Listener complianceListener;
private boolean useFilesForPartsWithoutFileName = true;
private Path filesDirectory;
Expand All @@ -411,9 +410,8 @@ public Parser(String boundary)
@Deprecated
public Parser(String boundary, MultiPartCompliance multiPartCompliance, ComplianceViolation.Listener complianceViolationListener)
{
compliance = Objects.requireNonNull(multiPartCompliance);
complianceListener = Objects.requireNonNull(complianceViolationListener);
parser = new MultiPart.Parser(Objects.requireNonNull(boundary), compliance, listener);
parser = new MultiPart.Parser(Objects.requireNonNull(boundary), Objects.requireNonNull(multiPartCompliance), listener);
}

/**
Expand Down Expand Up @@ -693,7 +691,7 @@ public void configure(MultiPartConfig config)
useFilesForPartsWithoutFileName = config.isUseFilesForPartsWithoutFileName();
filesDirectory = config.getLocation();
complianceListener = config.getViolationListener();
compliance = config.getMultiPartCompliance();
parser.setMultiPartCompliance(config.getMultiPartCompliance());
bufferPool = config.getBufferPool();
}

Expand Down Expand Up @@ -899,6 +897,7 @@ public void onFailure(Throwable failure)
@Override
public void onViolation(MultiPartCompliance.Violation violation)
{
MultiPartCompliance compliance = parser.getMultiPartCompliance();
boolean allowed = compliance.allows(violation);
ComplianceViolation.Event event = new ComplianceViolation.Event(compliance, violation, "multipart spec violation", allowed);
ComplianceUtils.notify(complianceListener, event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
Expand All @@ -32,14 +33,18 @@
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.io.content.AsyncContent;
import org.eclipse.jetty.io.content.ByteBufferContentSource;
import org.eclipse.jetty.io.content.InputStreamContentSource;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
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.ValueSource;

import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.US_ASCII;
Expand All @@ -48,6 +53,7 @@
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
Expand Down Expand Up @@ -429,6 +435,60 @@ public void testLFOnlyEOLStrict()
assertThat(ee.getCause().getMessage(), containsString("invalid LF-only EOL"));
}

@ParameterizedTest
@ValueSource(strings = {"RFC7578", "RFC7578_STRICT"})
public void testComplianceModeConfiguration(String spec) throws Exception
{
MultiPartCompliance compliance = MultiPartCompliance.from(spec);

String boundary = "boundary";
String str = """
--$B
Content-Disposition: form-data; name="greeting"
Content-Type: text/plain; charset=US-ASCII

Hello World
--$B--
""".replace("$B", boundary);

CaptureMultiPartViolations listener = new CaptureMultiPartViolations();
MultiPartConfig config = new MultiPartConfig.Builder()
.location(_tmpDir)
.maxMemoryPartSize(-1)
.complianceMode(compliance)
.violationListener(listener)
.build();

String contentType = "multipart/form-data; boundary=\"" + boundary + "\"";
Attributes attributes = new Attributes.Mapped();
Content.Source source = new ByteBufferContentSource(BufferUtil.toBuffer(str, UTF_8));
if (compliance.equals(MultiPartCompliance.RFC7578))
{
// RFC7578 tolerates LF line endings, parsing succeeds and the violation is reported.
try (MultiPartFormData.Parts parts = MultiPartFormData.getParts(source, attributes, contentType, config))
{
assertThat(parts.size(), is(1));
MultiPart.Part greeting = parts.getFirst("greeting");
assertThat(greeting, notNullValue());
assertThat(Content.Source.asString(greeting.getContentSource()), is("Hello World"));
}
assertThat(listener.getEvents().stream().map(ComplianceViolation.Event::violation).collect(Collectors.toList()),
hasItem(MultiPartCompliance.Violation.LF_LINE_TERMINATION));
}
else if (compliance.equals(MultiPartCompliance.RFC7578_STRICT))
{
// RFC7578_STRICT rejects LF line endings, the parsing must fail.
CompletionException ce = assertThrows(CompletionException.class,
() -> MultiPartFormData.getParts(source, attributes, contentType, config));
assertThat(ce.getCause(), instanceOf(HttpException.class));
assertThat(ce.getCause().getMessage(), containsString("invalid LF-only EOL"));
}
else
{
throw new IllegalStateException("Unexpected compliance mode: " + compliance);
}
}

/**
* Test of parsing where there is whitespace before the boundary.
*
Expand Down
Loading