From f5dffb0726353c8b0944e5ba5d96a882d6fd80cf Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Tue, 5 Dec 2017 21:46:11 +0000
Subject: [PATCH] Susimail, Console, Jetty: - Adjust multipart size limits -
 Better handling of errors when multipart limits are exceeded - Fix multipart
 config for /configplugins - Test for total size limit in susimail

---
 .../src/net/i2p/servlet/RequestWrapper.java   | 18 ++++++++
 .../net/i2p/servlet/filters/XSSFilter.java    | 11 ++++-
 apps/routerconsole/java/build.xml             |  2 +-
 apps/susimail/src/WEB-INF/web.xml             |  7 +--
 .../src/src/i2p/susi/webmail/Attachment.java  | 10 ++++-
 .../src/src/i2p/susi/webmail/WebMail.java     | 43 +++++++++++++------
 .../src/i2p/susi/webmail/smtp/SMTPClient.java | 14 ++++++
 7 files changed, 86 insertions(+), 19 deletions(-)

diff --git a/apps/jetty/java/src/net/i2p/servlet/RequestWrapper.java b/apps/jetty/java/src/net/i2p/servlet/RequestWrapper.java
index c0e06f9c1b..ffc7e306a4 100644
--- a/apps/jetty/java/src/net/i2p/servlet/RequestWrapper.java
+++ b/apps/jetty/java/src/net/i2p/servlet/RequestWrapper.java
@@ -101,6 +101,7 @@ public class RequestWrapper {
 
 	/**
 	 * @return List of request parameter names
+	 * @throws IllegalStateException if the request is too large
 	 */
 	public Enumeration<String> getParameterNames() {
 		if (isMultiPartRequest) {
@@ -117,6 +118,7 @@ public class RequestWrapper {
 					log(se);
 				} catch (IllegalStateException ise) {
 					log(ise);
+					throw ise;
 				}
 			}
 			return cachedParameterNames.keys();
@@ -139,6 +141,9 @@ public class RequestWrapper {
 		return httpRequest.getContentType();
 	}
 
+	/**
+	 * @throws IllegalStateException if the request is too large
+	 */
 	public String getContentType( String partName )
 	{
 		String result = null;
@@ -153,6 +158,7 @@ public class RequestWrapper {
 				log(se);
 			} catch (IllegalStateException ise) {
 				log(ise);
+				throw ise;
 			}
 		}
 		return result;
@@ -162,6 +168,9 @@ public class RequestWrapper {
 		return httpRequest.getAttribute( string );
 	}
 
+	/**
+	 * @throws IllegalStateException if the request is too large
+	 */
 	public String getParameter( String name, String defaultValue )
 	{
 		String result = defaultValue;
@@ -192,6 +201,7 @@ public class RequestWrapper {
 					log(se);
 				} catch (IllegalStateException ise) {
 					log(ise);
+					throw ise;
 				} finally {
 					if (in != null) try { in.close(); } catch (IOException ioe) {}
 				}
@@ -204,6 +214,9 @@ public class RequestWrapper {
 		return result;
 	}
 
+	/**
+	 * @throws IllegalStateException if the request is too large
+	 */
 	public String getFilename(String partName )
 	{
 		String result = null;
@@ -218,11 +231,15 @@ public class RequestWrapper {
 				log(se);
 			} catch (IllegalStateException ise) {
 				log(ise);
+				throw ise;
 			}
 		}
 		return result;
 	}
 
+	/**
+	 * @throws IllegalStateException if the request is too large
+	 */
 	public InputStream getInputStream(String partName )
 	{
 		InputStream result = null;
@@ -237,6 +254,7 @@ public class RequestWrapper {
 				log(se);
 			} catch (IllegalStateException ise) {
 				log(ise);
+				throw ise;
 			}
 		}
 		return result;
diff --git a/apps/jetty/java/src/net/i2p/servlet/filters/XSSFilter.java b/apps/jetty/java/src/net/i2p/servlet/filters/XSSFilter.java
index b29892e70f..9fcce1aba0 100644
--- a/apps/jetty/java/src/net/i2p/servlet/filters/XSSFilter.java
+++ b/apps/jetty/java/src/net/i2p/servlet/filters/XSSFilter.java
@@ -9,6 +9,7 @@ import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 /**
  *  @since 0.9.14
@@ -25,6 +26,14 @@ public class XSSFilter implements Filter {
     @Override
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
         throws IOException, ServletException {
-        chain.doFilter(new XSSRequestWrapper((HttpServletRequest) request), response);
+        try {
+            chain.doFilter(new XSSRequestWrapper((HttpServletRequest) request), response);
+        } catch (IllegalStateException ise) {
+            // Multipart form error, probably file too big
+            // We need to send the error quickly, if we just throw a ServletException,
+            // the data keeps coming and the connection gets reset.
+            // This way we at least get the error to the browser.
+            ((HttpServletResponse)response).sendError(413, ise.getMessage());
+        }
     }
 }
diff --git a/apps/routerconsole/java/build.xml b/apps/routerconsole/java/build.xml
index fc97aa97b8..7b1c2008fa 100644
--- a/apps/routerconsole/java/build.xml
+++ b/apps/routerconsole/java/build.xml
@@ -448,7 +448,7 @@
         <!-- Add multipart config to servlets that need them -->
         <property name="__match1" value="&lt;servlet-class&gt;net.i2p.router.web.jsp." />
         <property name="__match2" value="_jsp&lt;/servlet-class&gt;" />
-        <property name="__class1" value="${__match1}configclients${__match2}" />
+        <property name="__class1" value="${__match1}configplugins${__match2}" />
         <property name="__class2" value="${__match1}configfamily${__match2}" />
         <property name="__class3" value="${__match1}configreseed${__match2}" />
         <property name="__multipart" value="&#10;
diff --git a/apps/susimail/src/WEB-INF/web.xml b/apps/susimail/src/WEB-INF/web.xml
index 24d073f0e8..83b96a1e2b 100644
--- a/apps/susimail/src/WEB-INF/web.xml
+++ b/apps/susimail/src/WEB-INF/web.xml
@@ -17,9 +17,10 @@
     <servlet-name>SusiMail</servlet-name>
     <servlet-class>i2p.susi.webmail.WebMail</servlet-class>
     <multipart-config>
-      <max-file-size>67108864</max-file-size>
-      <max-request-size>67108864</max-request-size>
-      <file-size-threshold>262144</file-size-threshold>
+      <!-- 23 MB. See SMTPClient for discussion -->
+      <max-file-size>24117248</max-file-size>
+      <max-request-size>24117248</max-request-size>
+      <file-size-threshold>131072</file-size-threshold>
     </multipart-config>
   </servlet>
   <servlet-mapping>
diff --git a/apps/susimail/src/src/i2p/susi/webmail/Attachment.java b/apps/susimail/src/src/i2p/susi/webmail/Attachment.java
index 3cccc447cc..5fde459759 100644
--- a/apps/susimail/src/src/i2p/susi/webmail/Attachment.java
+++ b/apps/susimail/src/src/i2p/susi/webmail/Attachment.java
@@ -73,7 +73,15 @@ public class Attachment {
 	}
 
 	/**
-         * Delete the data file
+	 * The unencoded size
+	 * @since 0.9.33
+	 */
+	public long getSize() {
+		return data.length();
+	}
+
+	/**
+	 * Delete the data file
 	 * @since 0.9.33
 	 */
 	public void deleteData() {
diff --git a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java
index 044bb6f268..eec8f675be 100644
--- a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java
+++ b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java
@@ -1648,16 +1648,22 @@ public class WebMail extends HttpServlet
 			sessionObject.isMobile = isMobile;
 			
 			if (isPOST) {
-				String nonce = request.getParameter(SUSI_NONCE);
-				if (nonce == null || !sessionObject.isValidNonce(nonce)) {
-					// These two strings are already in the router console FormHandler,
-					// so translate with that bundle.
-					sessionObject.error = consoleGetString(
-						"Invalid form submission, probably because you used the 'back' or 'reload' button on your browser. Please resubmit.",
-						ctx)
-						+ '\n' +
-						consoleGetString("If the problem persists, verify that you have cookies enabled in your browser.",
-						ctx);
+				try {
+					String nonce = request.getParameter(SUSI_NONCE);
+					if (nonce == null || !sessionObject.isValidNonce(nonce)) {
+						// These two strings are already in the router console FormHandler,
+						// so translate with that bundle.
+						sessionObject.error = consoleGetString(
+							"Invalid form submission, probably because you used the 'back' or 'reload' button on your browser. Please resubmit.",
+							ctx)
+							+ '\n' +
+							consoleGetString("If the problem persists, verify that you have cookies enabled in your browser.",
+							ctx);
+						isPOST = false;
+					}
+				} catch (IllegalStateException ise) {
+					// too big, can't get any parameters
+					sessionObject.error += ise.getMessage() + '\n';
 					isPOST = false;
 				}
 			}
@@ -2027,6 +2033,19 @@ public class WebMail extends HttpServlet
 			sessionObject.error += "Internal error: Header line encoder not available.";
 		}
 
+		long total = text.length();
+		boolean multipart = sessionObject.attachments != null && !sessionObject.attachments.isEmpty();
+		if (multipart) {
+			for(Attachment a : sessionObject.attachments) {
+				total += a.getSize();
+			}
+		}
+		if (total > SMTPClient.BINARY_MAX_SIZE) {
+			ok = false;
+			sessionObject.error += _t("Email is too large, max is {0}",
+			                          DataHelper.formatSize2(SMTPClient.BINARY_MAX_SIZE, false) + 'B') + '\n';
+		}
+
 		if( ok ) {
 			StringBuilder body = new StringBuilder(1024);
 			body.append( "From: " + from + "\r\n" );
@@ -2040,9 +2059,7 @@ public class WebMail extends HttpServlet
 				sessionObject.error += e.getMessage();
 			}
 			String boundary = "_=" + I2PAppContext.getGlobalContext().random().nextLong();
-			boolean multipart = false;
-			if( sessionObject.attachments != null && !sessionObject.attachments.isEmpty() ) {
-				multipart = true;
+			if (multipart) {
 				body.append( "\r\nMIME-Version: 1.0\r\nContent-type: multipart/mixed; boundary=\"" + boundary + "\"\r\n\r\n" );
 			}
 			else {
diff --git a/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java b/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java
index 00f58f316b..2383625a85 100644
--- a/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java
+++ b/apps/susimail/src/src/i2p/susi/webmail/smtp/SMTPClient.java
@@ -47,6 +47,20 @@ import net.i2p.data.DataHelper;
  */
 public class SMTPClient {
 	
+	/**
+	 *  31.84 MB
+	 *  smtp.postman.i2p as of 2017-12.
+	 *  @since 0.9.33
+	 */
+	public static final long DEFAULT_MAX_SIZE = 33388608;
+
+	/**
+	 *  About 23.25 MB.
+	 *  Base64 encodes 57 chars to 76 + \r\n on a line
+	 *  @since 0.9.33
+	 */
+	public static final long BINARY_MAX_SIZE = (long) ((DEFAULT_MAX_SIZE * 57.0d / 78) - 32*1024);
+
 	private Socket socket;
 	public String error;
 	private String lastResponse;
-- 
GitLab