You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2017/10/03 11:59:03 UTC

[cxf] branch master updated: CXF-7507 - Put a configurable limit on the size of attachment headers

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

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/master by this push:
     new 8bd915b  CXF-7507 - Put a configurable limit on the size of attachment headers
8bd915b is described below

commit 8bd915bfd7735c248ad660059c6b6ad26cdbcdf6
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Tue Oct 3 11:53:04 2017 +0100

    CXF-7507 - Put a configurable limit on the size of attachment headers
---
 .../cxf/attachment/AttachmentDeserializer.java     | 24 ++++++++
 .../apache/cxf/attachment/ContentDisposition.java  |  2 +-
 .../attachment/HeaderSizeExceededException.java    | 40 ++++++++++++++
 .../java/org/apache/cxf/message/MessageUtils.java  | 22 ++++++++
 .../apache/cxf/jaxrs/ext/MessageContextImpl.java   |  7 +++
 .../apache/cxf/jaxrs/ext/multipart/Attachment.java |  6 +-
 .../cxf/systest/jaxrs/JAXRSMultipartTest.java      | 64 ++++++++++++++++++++++
 .../apache/cxf/systest/jaxrs/MultipartServer.java  |  1 +
 8 files changed, 162 insertions(+), 4 deletions(-)

diff --git a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
index 415e236..9fb1975 100644
--- a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
+++ b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
@@ -29,17 +29,20 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.logging.Logger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import javax.activation.DataSource;
 
+import org.apache.cxf.common.logging.LogUtils;
 import org.apache.cxf.common.util.StringUtils;
 import org.apache.cxf.helpers.HttpHeaderHelper;
 import org.apache.cxf.helpers.IOUtils;
 import org.apache.cxf.io.CachedOutputStream;
 import org.apache.cxf.message.Attachment;
 import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
 
 public class AttachmentDeserializer {
     public static final String ATTACHMENT_PART_HEADERS = AttachmentDeserializer.class.getName() + ".headers";
@@ -49,6 +52,12 @@ public class AttachmentDeserializer {
 
     public static final String ATTACHMENT_MAX_SIZE = "attachment-max-size";
 
+    /**
+     * The maximum MIME Header Length. The default is 300.
+     */
+    public static final String ATTACHMENT_MAX_HEADER_SIZE = "attachment-max-header-size";
+    public static final int DEFAULT_MAX_HEADER_SIZE = 300;
+
     public static final int THRESHOLD = 1024 * 100; //100K (byte unit)
 
     private static final Pattern CONTENT_TYPE_BOUNDARY_PATTERN = Pattern.compile("boundary=\"?([^\";]*)");
@@ -56,6 +65,8 @@ public class AttachmentDeserializer {
     private static final Pattern INPUT_STREAM_BOUNDARY_PATTERN =
             Pattern.compile("^--(\\S*)$", Pattern.MULTILINE);
 
+    private static final Logger LOG = LogUtils.getL7dLogger(AttachmentDeserializer.class);
+
     private boolean lazyLoading = true;
 
     private int pbAmount = 2048;
@@ -77,6 +88,8 @@ public class AttachmentDeserializer {
     private Set<DelegatingInputStream> loaded = new HashSet<>();
     private List<String> supportedTypes;
 
+    private int maxHeaderLength = DEFAULT_MAX_HEADER_SIZE;
+
     public AttachmentDeserializer(Message message) {
         this(message, Collections.singletonList("multipart/related"));
     }
@@ -84,6 +97,10 @@ public class AttachmentDeserializer {
     public AttachmentDeserializer(Message message, List<String> supportedTypes) {
         this.message = message;
         this.supportedTypes = supportedTypes;
+
+        // Get the maximum Header length from configuration
+        maxHeaderLength = MessageUtils.getContextualInteger(message, ATTACHMENT_MAX_HEADER_SIZE,
+                                                            DEFAULT_MAX_HEADER_SIZE);
     }
 
     public void initializeAttachments() throws IOException {
@@ -277,6 +294,7 @@ public class AttachmentDeserializer {
             new DelegatingInputStream(new MimeBodyPartInputStream(stream, boundary, pbAmount),
                                       this);
         createCount++;
+
         return AttachmentUtil.createAttachment(partStream, headers);
     }
 
@@ -325,6 +343,7 @@ public class AttachmentDeserializer {
         StringBuilder buffer = new StringBuilder(128);
         StringBuilder b = new StringBuilder(128);
         Map<String, List<String>> heads = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER);
+
         // loop until we hit the end or a null line
         while (readLine(in, b)) {
             // lines beginning with white space get special handling
@@ -371,6 +390,11 @@ public class AttachmentDeserializer {
                 // just add to the buffer
                 buffer.append((char)c);
             }
+
+            if (buffer.length() > maxHeaderLength) {
+                LOG.fine("The attachment header size has exceeded the configured parameter: " + maxHeaderLength);
+                throw new HeaderSizeExceededException();
+            }
         }
 
         // no characters found...this was either an eof or a null line.
diff --git a/core/src/main/java/org/apache/cxf/attachment/ContentDisposition.java b/core/src/main/java/org/apache/cxf/attachment/ContentDisposition.java
index 7cdda31..b70d3d9 100644
--- a/core/src/main/java/org/apache/cxf/attachment/ContentDisposition.java
+++ b/core/src/main/java/org/apache/cxf/attachment/ContentDisposition.java
@@ -28,7 +28,7 @@ import java.util.regex.Pattern;
 
 public class ContentDisposition {
     private static final String CD_HEADER_PARAMS_EXPRESSION =
-        "(([\\w-]+( )?\\*?=( )?\"[^\"]+\")|([\\w-]+( )?\\*?=( )?[^;]+))";
+       "[\\w-]++( )?\\*?=( )?((\"[^\"]++\")|([^;]+))";
     private static final Pattern CD_HEADER_PARAMS_PATTERN =
             Pattern.compile(CD_HEADER_PARAMS_EXPRESSION);
 
diff --git a/core/src/main/java/org/apache/cxf/attachment/HeaderSizeExceededException.java b/core/src/main/java/org/apache/cxf/attachment/HeaderSizeExceededException.java
new file mode 100644
index 0000000..69b484e
--- /dev/null
+++ b/core/src/main/java/org/apache/cxf/attachment/HeaderSizeExceededException.java
@@ -0,0 +1,40 @@
+/**
+ * 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.
+ */
+
+package org.apache.cxf.attachment;
+
+public class HeaderSizeExceededException extends RuntimeException {
+    private static final long serialVersionUID = -8976580055837650080L;
+
+    public HeaderSizeExceededException() {
+        super();
+    }
+
+    public HeaderSizeExceededException(String message) {
+        super(message);
+    }
+
+    public HeaderSizeExceededException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public HeaderSizeExceededException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/core/src/main/java/org/apache/cxf/message/MessageUtils.java b/core/src/main/java/org/apache/cxf/message/MessageUtils.java
index 2bdd509..5ed7787 100644
--- a/core/src/main/java/org/apache/cxf/message/MessageUtils.java
+++ b/core/src/main/java/org/apache/cxf/message/MessageUtils.java
@@ -19,8 +19,11 @@
 
 package org.apache.cxf.message;
 
+import java.util.logging.Logger;
+
 import org.w3c.dom.Node;
 
+import org.apache.cxf.common.logging.LogUtils;
 import org.apache.cxf.common.util.PropertyUtils;
 
 
@@ -29,6 +32,8 @@ import org.apache.cxf.common.util.PropertyUtils;
  */
 public final class MessageUtils {
 
+    private static final Logger LOG = LogUtils.getL7dLogger(MessageUtils.class);
+
     /**
      * Prevents instantiation.
      */
@@ -142,6 +147,23 @@ public final class MessageUtils {
         return defaultValue;
     }
 
+    public static int getContextualInteger(Message m, String key, int defaultValue) {
+        if (m != null) {
+            Object o = m.getContextualProperty(key);
+            if (o instanceof String) {
+                try {
+                    int i = Integer.parseInt((String)o);
+                    if (i > 0) {
+                        return i;
+                    }
+                } catch (NumberFormatException ex) {
+                    LOG.warning("Incorrect integer value of " + o + " specified for: " + key);
+                }
+            }
+        }
+        return defaultValue;
+    }
+
     public static Object getContextualProperty(Message m, String propPreferred, String propDefault) {
         Object prop = null;
         if (m != null) {
diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java
index d63d857..e7735fe 100644
--- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java
+++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java
@@ -45,6 +45,7 @@ import javax.ws.rs.ext.Providers;
 import org.apache.cxf.attachment.AttachmentDeserializer;
 import org.apache.cxf.attachment.AttachmentImpl;
 import org.apache.cxf.attachment.AttachmentUtil;
+import org.apache.cxf.attachment.HeaderSizeExceededException;
 import org.apache.cxf.endpoint.Endpoint;
 import org.apache.cxf.helpers.CastUtils;
 import org.apache.cxf.interceptor.AttachmentOutInterceptor;
@@ -64,6 +65,7 @@ import org.apache.cxf.message.MessageImpl;
 import org.apache.cxf.message.MessageUtils;
 
 public class MessageContextImpl implements MessageContext {
+
     private Message m;
     public MessageContextImpl(Message m) {
         this.m = m;
@@ -78,6 +80,8 @@ public class MessageContextImpl implements MessageContext {
             } catch (CacheSizeExceededException e) {
                 m.getExchange().put("cxf.io.cacheinput", Boolean.FALSE);
                 throw new WebApplicationException(e, 413);
+            } catch (HeaderSizeExceededException e) {
+                throw new WebApplicationException(e, 413);
             }
         }
         if (keyValue.equals("WRITE-" + Message.ATTACHMENTS)) {
@@ -268,6 +272,8 @@ public class MessageContextImpl implements MessageContext {
                 m.getExchange().getInMessage().get(AttachmentDeserializer.ATTACHMENT_MEMORY_THRESHOLD));
             inMessage.put(AttachmentDeserializer.ATTACHMENT_MAX_SIZE,
                 m.getExchange().getInMessage().get(AttachmentDeserializer.ATTACHMENT_MAX_SIZE));
+            inMessage.put(AttachmentDeserializer.ATTACHMENT_MAX_HEADER_SIZE,
+                m.getExchange().getInMessage().get(AttachmentDeserializer.ATTACHMENT_MAX_HEADER_SIZE));
             inMessage.setContent(InputStream.class,
                 m.getExchange().getInMessage().get("org.apache.cxf.multipart.embedded.input"));
             inMessage.put(Message.CONTENT_TYPE,
@@ -281,6 +287,7 @@ public class MessageContextImpl implements MessageContext {
         try {
             Map<String, List<String>> headers
                 = CastUtils.cast((Map<?, ?>)inMessage.get(AttachmentDeserializer.ATTACHMENT_PART_HEADERS));
+
             Attachment first = new Attachment(AttachmentUtil.createAttachment(
                                      inMessage.getContent(InputStream.class),
                                      headers),
diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java
index 1ae0019..fbe8ff4 100644
--- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java
+++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java
@@ -93,7 +93,7 @@ public class Attachment implements Transferable {
     public Attachment(String mediaType, Object object) {
         this(UUID.randomUUID().toString(), mediaType, object);
     }
-    
+
     public Attachment(String id, String mediaType, Object object) {
         this.object = object;
         if (id != null) {
@@ -111,7 +111,7 @@ public class Attachment implements Transferable {
         headers.putSingle("Content-Type", "application/octet-stream");
     }
 
-    Attachment(MultivaluedMap<String, String> headers, DataHandler handler, Object object) {
+    public Attachment(MultivaluedMap<String, String> headers, DataHandler handler, Object object) {
         this.headers = headers;
         this.handler = handler;
         this.object = object;
@@ -128,7 +128,7 @@ public class Attachment implements Transferable {
     }
 
     public MediaType getContentType() {
-        String value = handler != null && handler.getContentType() != null ? handler.getContentType() 
+        String value = handler != null && handler.getContentType() != null ? handler.getContentType()
             : headers.getFirst("Content-Type");
         return value == null ? MediaType.TEXT_PLAIN_TYPE : JAXRSUtils.toMediaType(value);
     }
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java
index a4c5791..406da79 100644
--- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java
@@ -39,6 +39,7 @@ import javax.activation.DataHandler;
 import javax.imageio.ImageIO;
 import javax.mail.util.ByteArrayDataSource;
 import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import javax.xml.bind.JAXBContext;
@@ -53,6 +54,7 @@ import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.cxf.jaxrs.ext.multipart.Attachment;
 import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition;
+import org.apache.cxf.jaxrs.ext.multipart.InputStreamDataSource;
 import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
 import org.apache.cxf.jaxrs.impl.MetadataMap;
 import org.apache.cxf.jaxrs.provider.json.JSONProvider;
@@ -937,6 +939,67 @@ public class JAXRSMultipartTest extends AbstractBusClientServerTestBase {
         }
     }
 
+    // The large Content Disposition header will be rejected here
+    @Test
+    public void testLargeHeader() throws Exception {
+        InputStream is1 =
+            getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/java.jpg");
+        String address = "http://localhost:" + PORT + "/bookstore/books/image";
+        WebClient client = WebClient.create(address);
+        client.type("multipart/mixed").accept("multipart/mixed");
+        WebClient.getConfig(client).getRequestContext().put("support.type.as.multipart",
+            "true");
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("form-data;");
+        for (int i = 0; i < 10000; i++) {
+            sb.append("aaaaaaaaaa");
+        }
+
+        MultivaluedMap<String, String> headers = new MultivaluedHashMap<>();
+        headers.putSingle("Content-ID", "root");
+        headers.putSingle("Content-Type", "application/octet-stream");
+        headers.putSingle("Content-Disposition", sb.toString());
+        DataHandler handler = new DataHandler(new InputStreamDataSource(is1, "application/octet-stream"));
+
+        Attachment att = new Attachment(headers, handler, null);
+        Response response = client.post(att);
+        assertEquals(response.getStatus(), 413);
+
+        client.close();
+    }
+
+    // The Content Disposition header will be accepted here, even though it is larger than the default,
+    // as we have configured a larger value on the service side
+    @Test
+    public void testLargerThanDefaultHeader() throws Exception {
+        InputStream is1 =
+            getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/java.jpg");
+        String address = "http://localhost:" + PORT + "/bookstore/books/image";
+        WebClient client = WebClient.create(address);
+        client.type("multipart/mixed").accept("multipart/mixed");
+        WebClient.getConfig(client).getRequestContext().put("support.type.as.multipart",
+            "true");
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("form-data;");
+        for (int i = 0; i < 35; i++) {
+            sb.append("aaaaaaaaaa");
+        }
+
+        MultivaluedMap<String, String> headers = new MultivaluedHashMap<>();
+        headers.putSingle("Content-ID", "root");
+        headers.putSingle("Content-Type", "application/octet-stream");
+        headers.putSingle("Content-Disposition", sb.toString());
+        DataHandler handler = new DataHandler(new InputStreamDataSource(is1, "application/octet-stream"));
+
+        Attachment att = new Attachment(headers, handler, null);
+        Response response = client.post(att);
+        assertEquals(response.getStatus(), 200);
+
+        client.close();
+    }
+
     private void doAddBook(String address, String resourceName, int status) throws Exception {
         doAddBook("multipart/related", address, resourceName, status);
     }
@@ -1018,4 +1081,5 @@ public class JAXRSMultipartTest extends AbstractBusClientServerTestBase {
         }
         return str;
     }
+
 }
diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartServer.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartServer.java
index b3b923e..b519d56 100644
--- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartServer.java
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartServer.java
@@ -40,6 +40,7 @@ public class MultipartServer extends AbstractBusTestServerBase {
         Map<String, Object> props = new HashMap<>();
         props.put(AttachmentDeserializer.ATTACHMENT_MAX_SIZE, String.valueOf(1024 * 10));
         props.put(AttachmentDeserializer.ATTACHMENT_MEMORY_THRESHOLD, String.valueOf(1024 * 5));
+        props.put(AttachmentDeserializer.ATTACHMENT_MAX_HEADER_SIZE, String.valueOf(400));
         sf.setProperties(props);
         //default lifecycle is per-request, change it to singleton
         sf.setResourceProvider(MultipartStore.class,

-- 
To stop receiving notification emails like this one, please contact
['"commits@cxf.apache.org" <co...@cxf.apache.org>'].