You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2022/11/13 11:30:43 UTC

[httpcomponents-client] 02/03: Regression: Multipart body builder and multipart formatters fail to escape special characters such as backslash and quote mark

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit 32228cd4ff6cccc3d4d7889036de879198a8e5b5
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Mon Nov 7 14:39:32 2022 +0100

    Regression: Multipart body builder and multipart formatters fail to escape special characters such as backslash and quote mark
---
 .../http/entity/mime/AbstractMultipartFormat.java  | 47 ++++++++++++++++---
 .../hc/client5/http/entity/mime/MimeField.java     |  9 +++-
 .../hc/client5/http/entity/mime/TestMimeField.java | 51 +++++++++++++++++++++
 .../http/entity/mime/TestMultipartFormat.java      | 53 ++++++++++++++++++++++
 4 files changed, 152 insertions(+), 8 deletions(-)

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 66b373b9a..3f3c2c8e0 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
@@ -47,7 +47,7 @@ import org.apache.hc.core5.util.ByteArrayBuffer;
 abstract class AbstractMultipartFormat {
 
     static ByteArrayBuffer encode(
-            final Charset charset, final String string) {
+            final Charset charset, final CharSequence string) {
         final ByteBuffer encoded = charset.encode(CharBuffer.wrap(string));
         final ByteArrayBuffer bab = new ByteArrayBuffer(encoded.remaining());
         bab.append(encoded.array(), encoded.arrayOffset() + encoded.position(), encoded.remaining());
@@ -60,30 +60,63 @@ abstract class AbstractMultipartFormat {
     }
 
     static void writeBytes(
-            final String s, final Charset charset, final OutputStream out) throws IOException {
+            final CharSequence s, final Charset charset, final OutputStream out) throws IOException {
         final ByteArrayBuffer b = encode(charset, s);
         writeBytes(b, out);
     }
 
     static void writeBytes(
-            final String s, final OutputStream out) throws IOException {
+            final CharSequence s, final OutputStream out) throws IOException {
         final ByteArrayBuffer b = encode(StandardCharsets.ISO_8859_1, s);
         writeBytes(b, out);
     }
 
+    static boolean isLineBreak(final char ch) {
+        return ch == '\r' || ch == '\n' || ch == '\f' || ch == 11;
+    }
+
+    static CharSequence stripLineBreaks(final CharSequence s) {
+        if (s == null) {
+            return null;
+        }
+        boolean requiresRewrite = false;
+        int n = 0;
+        for (; n < s.length(); n++) {
+            final char ch = s.charAt(n);
+            if (isLineBreak(ch)) {
+                requiresRewrite = true;
+                break;
+            }
+        }
+        if (!requiresRewrite) {
+            return s;
+        }
+        final StringBuilder buf = new StringBuilder();
+        buf.append(s, 0, n);
+        for (; n < s.length(); n++) {
+            final char ch = s.charAt(n);
+            if (isLineBreak(ch)) {
+                buf.append(' ');
+            } else {
+                buf.append(ch);
+            }
+        }
+        return buf.toString();
+    }
+
     static void writeField(
             final MimeField field, final OutputStream out) throws IOException {
-        writeBytes(field.getName(), out);
+        writeBytes(stripLineBreaks(field.getName()), out);
         writeBytes(FIELD_SEP, out);
-        writeBytes(field.getBody(), out);
+        writeBytes(stripLineBreaks(field.getBody()), out);
         writeBytes(CR_LF, out);
     }
 
     static void writeField(
             final MimeField field, final Charset charset, final OutputStream out) throws IOException {
-        writeBytes(field.getName(), charset, out);
+        writeBytes(stripLineBreaks(field.getName()), charset, out);
         writeBytes(FIELD_SEP, out);
-        writeBytes(field.getBody(), charset, out);
+        writeBytes(stripLineBreaks(field.getBody()), charset, out);
         writeBytes(CR_LF, out);
     }
 
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MimeField.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MimeField.java
index 87e9e9620..4fbbee4bc 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MimeField.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MimeField.java
@@ -84,7 +84,14 @@ public class MimeField {
             sb.append("; ");
             sb.append(parameter.getName());
             sb.append("=\"");
-            sb.append(parameter.getValue());
+            final String v = parameter.getValue();
+            for (int n = 0; n < v.length(); n++) {
+                final char ch = v.charAt(n);
+                if (ch == '"' || ch == '\\' ) {
+                    sb.append("\\");
+                }
+                sb.append(ch);
+            }
             sb.append("\"");
         }
         return sb.toString();
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMimeField.java b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMimeField.java
new file mode 100644
index 000000000..2aa1e24b0
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMimeField.java
@@ -0,0 +1,51 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.entity.mime;
+
+import java.util.Arrays;
+
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.message.BasicNameValuePair;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestMimeField {
+
+    @Test
+    public void testBasics() {
+        final MimeField f1 = new MimeField("some-field", "some-value",
+                Arrays.<NameValuePair>asList(
+                        new BasicNameValuePair("p1", "this"),
+                        new BasicNameValuePair("p2", "that"),
+                        new BasicNameValuePair("p3", "\"this \\and\\ that\"")));
+        Assertions.assertEquals("some-field", f1.getName());
+        Assertions.assertEquals("some-value", f1.getValue());
+        Assertions.assertEquals("some-value; p1=\"this\"; p2=\"that\"; p3=\"\\\"this \\\\and\\\\ that\\\"\"", f1.getBody());
+    }
+
+}
\ No newline at end of file
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartFormat.java b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartFormat.java
new file mode 100644
index 000000000..eee183c12
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartFormat.java
@@ -0,0 +1,53 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.entity.mime;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestMultipartFormat {
+
+    @Test
+    public void testLineBreak() {
+        Assertions.assertTrue(AbstractMultipartFormat.isLineBreak('\r'));
+        Assertions.assertTrue(AbstractMultipartFormat.isLineBreak('\n'));
+        Assertions.assertTrue(AbstractMultipartFormat.isLineBreak('\f'));
+        Assertions.assertTrue(AbstractMultipartFormat.isLineBreak((char) 11));
+        Assertions.assertFalse(AbstractMultipartFormat.isLineBreak(' '));
+        Assertions.assertFalse(AbstractMultipartFormat.isLineBreak('x'));
+    }
+
+    @Test
+    public void testLineBreakRewrite() {
+        final String s = "blah blah blah";
+        Assertions.assertSame(s, AbstractMultipartFormat.stripLineBreaks(s));
+        Assertions.assertEquals("blah blah blah ", AbstractMultipartFormat.stripLineBreaks("blah\rblah\nblah\f"));
+        Assertions.assertEquals("    f", AbstractMultipartFormat.stripLineBreaks("\r\n\r\nf"));
+    }
+
+}
\ No newline at end of file