diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/AbstractMultipartFormat.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/AbstractMultipartFormat.java index 3f106b7518..442bd55b03 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/AbstractMultipartFormat.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/AbstractMultipartFormat.java @@ -114,6 +114,21 @@ static CharSequence stripLineBreaks(final CharSequence s) { return buf.toString(); } + static CharSequence escapeQuoted(final CharSequence s) { + if (s == null) { + return null; + } + final StringBuilder buf = new StringBuilder(s.length()); + for (int n = 0; n < s.length(); n++) { + final char ch = s.charAt(n); + if (ch == '"' || ch == '\\') { + buf.append('\\'); + } + buf.append(ch); + } + return buf.toString(); + } + static void writeField( final MimeField field, final OutputStream out) throws IOException { writeBytes(stripLineBreaks(field.getName()), out); diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java index 5b2167245a..c4e21e667a 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java @@ -119,7 +119,7 @@ protected void formatMultipartHeader(final MultipartPart part, final OutputStrea writeBytes(value, StandardCharsets.ISO_8859_1, out); } } else { - writeBytes(value, out); + writeBytes(stripLineBreaks(escapeQuoted(value)), out); } } writeBytes("\"", out); diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578MultipartTest.java b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578MultipartTest.java index 91d71d2997..797171efb5 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578MultipartTest.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578MultipartTest.java @@ -28,12 +28,37 @@ package org.apache.hc.client5.http.entity.mime; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; + +import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.net.PercentCodec; import org.junit.jupiter.api.Test; class HttpRFC7578MultipartTest { + @Test + void testFieldNameLineBreaksAndQuotesAreNeutralised() throws Exception { + final FormBodyPart part = FormBodyPartBuilder.create( + "evil\"\r\nX-Injected: yes", + new StringBody("value", ContentType.DEFAULT_TEXT), + HttpMultipartMode.EXTENDED).build(); + final HttpRFC7578Multipart multipart = new HttpRFC7578Multipart( + StandardCharsets.UTF_8, "BOUNDARY", Collections.singletonList(part), + HttpMultipartMode.EXTENDED); + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + multipart.writeTo(out); + final String s = out.toString("ISO-8859-1"); + + assertFalse(s.contains("\r\nX-Injected: yes"), s); + assertTrue(s.contains("name=\"evil\\\" X-Injected: yes\""), s); + } + @Test void testPercentDecodingWithValidMessages() { final String[][] tests = new String[][] {