Author: norman
Date: Fri Feb  4 19:18:53 2011
New Revision: 1067263

URL: http://svn.apache.org/viewvc?rev=1067263&view=rev
Log:
Stream big literal to disk if a configurable threshold was hit. This guard 
against DOS. The default is 10MB which should make it still very performant by 
default as we only need to hit the disk in a few cases. See JAMES-1191

Added:
    
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/AbstractNettyImapRequestLineReader.java
    
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/NettyStreamImapRequestLineReader.java
Modified:
    james/server/trunk/container-spring/src/main/config/james/imapserver.xml
    
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
    
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/ImapRequestFrameDecoder.java
    
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/NettyImapRequestLineReader.java

Modified: 
james/server/trunk/container-spring/src/main/config/james/imapserver.xml
URL: 
http://svn.apache.org/viewvc/james/server/trunk/container-spring/src/main/config/james/imapserver.xml?rev=1067263&r1=1067262&r2=1067263&view=diff
==============================================================================
--- james/server/trunk/container-spring/src/main/config/james/imapserver.xml 
(original)
+++ james/server/trunk/container-spring/src/main/config/james/imapserver.xml 
Fri Feb  4 19:18:53 2011
@@ -31,6 +31,9 @@
   <!-- Maximal allowed line-length before a BAD response will get returned to 
the client -->
   <!-- This should be set with caution as a to high value can make the server 
a target for DOS! -->
   <maxLineLength>65536</maxLineLength>
+  
+  <!-- 10MB size limit before we will start to stream to a temporary file -->
+  <inMemorySizeLimit>10485760</inMemorySizeLimit>
   <handler>
       <connectionLimit> 0 </connectionLimit>
       <connectionLimitPerIP> 0 </connectionLimitPerIP>

Added: 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/AbstractNettyImapRequestLineReader.java
URL: 
http://svn.apache.org/viewvc/james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/AbstractNettyImapRequestLineReader.java?rev=1067263&view=auto
==============================================================================
--- 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/AbstractNettyImapRequestLineReader.java
 (added)
+++ 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/AbstractNettyImapRequestLineReader.java
 Fri Feb  4 19:18:53 2011
@@ -0,0 +1,47 @@
+/****************************************************************
+ * 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.james.imapserver.netty;
+
+import org.apache.james.imap.decode.DecodingException;
+import org.apache.james.imap.decode.ImapRequestLineReader;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.jboss.netty.channel.Channel;
+
+public abstract class AbstractNettyImapRequestLineReader extends 
ImapRequestLineReader{
+    private Channel channel;
+    private ChannelBuffer cRequest = 
ChannelBuffers.wrappedBuffer("+\r\n".getBytes());
+    private boolean retry;
+    
+    public AbstractNettyImapRequestLineReader(Channel channel, boolean retry) {
+        this.channel = channel;
+        this.retry = retry;
+        
+    }
+    /*
+     * (non-Javadoc)
+     * @see 
org.apache.james.imap.decode.ImapRequestLineReader#commandContinuationRequest()
+     */
+    protected void commandContinuationRequest() throws DecodingException {
+        // only write the request out if this is not a retry to process the 
request..
+        
+        if (!retry) channel.write(cRequest);
+    }
+    
+}

Modified: 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
URL: 
http://svn.apache.org/viewvc/james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java?rev=1067263&r1=1067262&r2=1067263&view=diff
==============================================================================
--- 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
 (original)
+++ 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
 Fri Feb  4 19:18:53 2011
@@ -59,9 +59,14 @@ public class IMAPServer extends Abstract
     private boolean compress;
 
     private int maxLineLength;
+
+    private int inMemorySizeLimit;
     
     // Use a big default
     public final static int DEFAULT_MAX_LINE_LENGTH = 65536;
+    
+    // Use 10MB as default
+    public final static int DEFAULT_IN_MEMORY_SIZE_LIMIT = 10485760;
 
     @Resource(name="imapDecoder")
     public void setImapDecoder(ImapDecoder decoder) {
@@ -84,6 +89,7 @@ public class IMAPServer extends Abstract
         hello  = softwaretype + " Server " + getHelloName() + " is ready.";
         compress = configuration.getBoolean("compress", false);
         maxLineLength = configuration.getInt("maxLineLength", 
DEFAULT_MAX_LINE_LENGTH);
+        inMemorySizeLimit = configuration.getInt("inMemorySizeLimit", 
DEFAULT_IN_MEMORY_SIZE_LIMIT);
     }
     
     
@@ -125,7 +131,7 @@ public class IMAPServer extends Abstract
                 
                 // Add the text line decoder which limit the max line length, 
don't strip the delimiter and use CRLF as delimiter
                 pipeline.addLast(FRAMER, new 
DelimiterBasedFrameDecoder(maxLineLength, false, Delimiters.lineDelimiter()));
-                pipeline.addLast(REQUEST_DECODER, new 
ImapRequestFrameDecoder(decoder));
+                pipeline.addLast(REQUEST_DECODER, new 
ImapRequestFrameDecoder(decoder, inMemorySizeLimit));
 
                 
                 if (isSSLSocket()) {

Modified: 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/ImapRequestFrameDecoder.java
URL: 
http://svn.apache.org/viewvc/james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/ImapRequestFrameDecoder.java?rev=1067263&r1=1067262&r2=1067263&view=diff
==============================================================================
--- 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/ImapRequestFrameDecoder.java
 (original)
+++ 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/ImapRequestFrameDecoder.java
 Fri Feb  4 19:18:53 2011
@@ -19,14 +19,25 @@
 
 package org.apache.james.imapserver.netty;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
 import org.apache.james.imap.api.ImapMessage;
 import org.apache.james.imap.api.process.ImapSession;
 import org.apache.james.imap.decode.ImapDecoder;
 import org.apache.james.imap.decode.ImapRequestLineReader;
 import org.apache.james.protocols.impl.ChannelAttributeSupport;
 import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBufferInputStream;
 import org.jboss.netty.channel.Channel;
 import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
 import org.jboss.netty.handler.codec.frame.FrameDecoder;
 
 /**
@@ -35,47 +46,126 @@ import org.jboss.netty.handler.codec.fra
  */
 public class ImapRequestFrameDecoder extends FrameDecoder implements 
ChannelAttributeSupport{
 
-    private ImapDecoder decoder;
+    private final ImapDecoder decoder;
+    private final int inMemorySizeLimit;
+    private final static String NEEDED_DATA = "NEEDED_DATA";
+    private final static String STORED_DATA = "STORED_DATA";
+    private final static String WRITTEN_DATA = "WRITTEN_DATA";
 
-    public ImapRequestFrameDecoder(ImapDecoder decoder) {
+    public ImapRequestFrameDecoder(ImapDecoder decoder, int inMemorySizeLimit) 
{
         this.decoder = decoder;
+        this.inMemorySizeLimit = inMemorySizeLimit;
     }
     
+    @Override
+    public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) 
throws Exception {
+        ctx.setAttachment(new HashMap<String, Object>());
+        super.channelOpen(ctx, e);
+    }
+
     /*
      * (non-Javadoc)
      * @see 
org.jboss.netty.handler.codec.frame.FrameDecoder#decode(org.jboss.netty.channel.ChannelHandlerContext,
 org.jboss.netty.channel.Channel, org.jboss.netty.buffer.ChannelBuffer)
      */
+    @SuppressWarnings("unchecked")
     protected Object decode(ChannelHandlerContext ctx, Channel channel, 
ChannelBuffer buffer) throws Exception {
         buffer.markReaderIndex();
         boolean retry = false;
+       
+        ImapRequestLineReader reader;
         // check if we failed before and if we already know how much data we 
need to sucess next run
-        Object attachment = ctx.getAttachment();
-        if (attachment != null) {
+        Map<String,Object> attachment = (Map<String, Object>) 
ctx.getAttachment();
+       
+        if (attachment.containsKey(NEEDED_DATA)) {
             retry = true;
-            int size = (Integer) attachment;
+            int size = (Integer) attachment.get(NEEDED_DATA);
             // now see if the buffer hold enough data to process.
             if (size != 
NettyImapRequestLineReader.NotEnoughDataException.UNKNOWN_SIZE && size > 
buffer.readableBytes()) {
-                buffer.resetReaderIndex();
                 
-                return null;
+                // check if we have a inMemorySize limit and if so if the 
expected size will fit into it
+                if (inMemorySizeLimit > 0 && inMemorySizeLimit < size) {
+                    
+                    // ok seems like it will not fit in the memory limit so we 
need to store it in a temporary file
+                    final File f;
+                    int written;
+                    
+                    // check if we have created a temporary file already or if 
we need to create a new one
+                    if (attachment.containsKey(STORED_DATA)) {
+                        f = (File) attachment.get(STORED_DATA);
+                        written = (Integer) attachment.get(WRITTEN_DATA);
+                    } else {
+                        f = File.createTempFile("imap-literal", ".tmp");
+                        attachment.put(STORED_DATA, f);
+                        written = 0;
+                        attachment.put(WRITTEN_DATA, written);
+
+                    }
+                    
+                   
+                    InputStream bufferIn = null; 
+                    OutputStream out = null;
+                    try {
+                        bufferIn = new ChannelBufferInputStream(buffer);
+                        out = new FileOutputStream(f, true);
+                        
+                        // write the needed data to the file
+                        int i = -1;
+                        while (written < size && (i = bufferIn.read()) != -1) {
+                           out.write(i);
+                           written++;
+                        }
+                        
+                       
+                        
+                    } finally {
+                       if (bufferIn != null) {
+                           bufferIn.close();
+                       }
+                       if (out != null) {
+                           out.close();
+                       }
+                    }                    
+                    // Check if all needed data was streamed to the file. 
+                    if (written == size) {
+                        reader = new NettyStreamImapRequestLineReader(channel, 
new FileInputStream(f) {
+                            /**
+                             * Delete the File on close too
+                             */
+                            @Override
+                            public void close() throws IOException {
+                                super.close();
+                                f.delete();
+                            }
+                            
+                        }, retry); 
+                    } else {
+                        attachment.put(WRITTEN_DATA, written);
+                        return null;
+                    }
+                    
+                } else {
+                    buffer.resetReaderIndex();
+                    return null;
+                }
+                
+            } else {
+                
+                reader = new NettyImapRequestLineReader(channel, buffer, 
retry);
             }
+        } else {
+            reader = new NettyImapRequestLineReader(channel, buffer, retry);
         }
         
-        try {
-            
-            ImapRequestLineReader reader = new 
NettyImapRequestLineReader(channel, buffer, retry);
+        try {      
             ImapMessage message = decoder.decode(reader, (ImapSession) 
attributes.get(channel));
 
-            // ok no errors found consume the rest of the line
-            reader.consumeLine();
-
-            ctx.setAttachment(null);
+            attachment.clear();
             return message;
         } catch (NettyImapRequestLineReader.NotEnoughDataException e) {
             // this exception was thrown because we don't have enough data yet 
             int neededData = e.getNeededSize();
             // store the needed data size for later usage
-            ctx.setAttachment(neededData);
+            attachment.put(NEEDED_DATA, neededData);
             buffer.resetReaderIndex();
             return null;
         }

Modified: 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/NettyImapRequestLineReader.java
URL: 
http://svn.apache.org/viewvc/james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/NettyImapRequestLineReader.java?rev=1067263&r1=1067262&r2=1067263&view=diff
==============================================================================
--- 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/NettyImapRequestLineReader.java
 (original)
+++ 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/NettyImapRequestLineReader.java
 Fri Feb  4 19:18:53 2011
@@ -26,7 +26,6 @@ import org.apache.james.imap.decode.Imap
 import org.apache.james.imap.decode.base.EolInputStream;
 import org.jboss.netty.buffer.ChannelBuffer;
 import org.jboss.netty.buffer.ChannelBufferInputStream;
-import org.jboss.netty.buffer.ChannelBuffers;
 import org.jboss.netty.channel.Channel;
 
 /**
@@ -34,17 +33,14 @@ import org.jboss.netty.channel.Channel;
  * see the docs on {@link #nextChar()} and {@link #read(int)} to understand 
the special behavior of this implementation
  *
  */
-public class NettyImapRequestLineReader extends ImapRequestLineReader{
+public class NettyImapRequestLineReader extends 
AbstractNettyImapRequestLineReader{
 
     private ChannelBuffer buffer;
-    private Channel channel;
-    private ChannelBuffer cRequest = 
ChannelBuffers.wrappedBuffer("+\r\n".getBytes());
-    private boolean retry;
+    private int read = 0;
     
     public NettyImapRequestLineReader(Channel channel, ChannelBuffer buffer, 
boolean retry) {
+        super(channel, retry);
         this.buffer = buffer;
-        this.channel = channel;
-        this.retry = retry;
         
     }
 
@@ -61,6 +57,7 @@ public class NettyImapRequestLineReader 
 
             if (buffer.readable()) {
                 next = buffer.readByte();
+                read++;
             } else {
                 throw new NotEnoughDataException();
             }
@@ -81,7 +78,8 @@ public class NettyImapRequestLineReader 
         }
         // Check if we have enough data
         if (size  + crlf> buffer.readableBytes()) {
-            throw new NotEnoughDataException(size);
+            // ok let us throw a exception which till the decoder how many 
more bytes we need
+            throw new NotEnoughDataException(size + read + crlf);
         }
         
         // Unset the next char.
@@ -96,16 +94,7 @@ public class NettyImapRequestLineReader 
         }
     }
 
-    /*
-     * (non-Javadoc)
-     * @see 
org.apache.james.imap.decode.ImapRequestLineReader#commandContinuationRequest()
-     */
-    protected void commandContinuationRequest() throws DecodingException {
-        // only write the request out if this is not a retry to process the 
request..
-        
-        if (!retry) channel.write(cRequest);
-    }
-    
+
     /**
      * {@link RuntimeException} which will get thrown by {@link 
NettyImapRequestLineReader#nextChar()} and {@link 
NettyImapRequestLineReader#read(int)} if not 
      * enough data is readable in the underlying {@link ChannelBuffer}

Added: 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/NettyStreamImapRequestLineReader.java
URL: 
http://svn.apache.org/viewvc/james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/NettyStreamImapRequestLineReader.java?rev=1067263&view=auto
==============================================================================
--- 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/NettyStreamImapRequestLineReader.java
 (added)
+++ 
james/server/trunk/imapserver/src/main/java/org/apache/james/imapserver/netty/NettyStreamImapRequestLineReader.java
 Fri Feb  4 19:18:53 2011
@@ -0,0 +1,101 @@
+/****************************************************************
+ * 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.james.imapserver.netty;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.james.imap.api.display.HumanReadableText;
+import org.apache.james.imap.decode.DecodingException;
+import org.apache.james.imap.decode.base.EolInputStream;
+import org.apache.james.imap.decode.base.FixedLengthInputStream;
+import org.jboss.netty.channel.Channel;
+
+public class NettyStreamImapRequestLineReader  extends 
AbstractNettyImapRequestLineReader{
+
+    private InputStream in;
+
+    public NettyStreamImapRequestLineReader(Channel channel, InputStream in, 
boolean retry) {
+        super(channel, retry);
+        this.in = in;
+    }
+
+    /**
+     * Reads the next character in the current line. This method will continue
+     * to return the same character until the {@link #consume()} method is
+     * called.
+     * 
+     * @return The next character TODO: character encoding is variable and
+     *         cannot be determine at the token level; this char is not 
accurate
+     *         reported; should be an octet
+     * @throws DecodingException
+     *             If the end-of-stream is reached.
+     */
+    public char nextChar() throws DecodingException {
+        if (!nextSeen) {
+            int next = -1;
+
+            try {
+                next = in.read();
+            } catch (IOException e) {
+                throw new 
DecodingException(HumanReadableText.SOCKET_IO_FAILURE, 
+                        "Error reading from stream.", e);
+            }
+            if (next == -1) {
+                throw new 
DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, 
+                        "Unexpected end of stream.");
+            }
+
+            nextSeen = true;
+            nextChar = (char) next;
+        }
+        return nextChar;
+    }
+
+
+    /**
+     * Reads and consumes a number of characters from the underlying reader,
+     * filling the char array provided. TODO: remove unnecessary copying of
+     * bits; line reader should maintain an internal ByteBuffer;
+     * 
+     * @param holder
+     *            A char array which will be filled with chars read from the
+     *            underlying reader.
+     * @throws DecodingException
+     *             If a char can't be read into each array element.
+     */
+    public InputStream read(int size, boolean extraCRLF) throws 
DecodingException {
+
+        // Unset the next char.
+        nextSeen = false;
+        nextChar = 0;
+        FixedLengthInputStream fin = new FixedLengthInputStream(this.in, size);
+        if (extraCRLF) {
+           return new EolInputStream(this, fin);
+        } else {
+            return fin;
+        }
+    }
+    
+    public void dispose() throws IOException {
+        in.close();
+    }
+    
+
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to