Merge remote-tracking branch 'origin/master'
diff --git a/core/src/main/java/fi/iki/elonen/NanoHTTPD.java b/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
index 847aee2..bf58c7b 100644
--- a/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
+++ b/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
@@ -475,6 +475,58 @@
 
     private static final Pattern BOUNDARY_PATTERN = Pattern.compile(BOUNDARY_REGEX, Pattern.CASE_INSENSITIVE);
 
+    /**
+     * Creates a normal ServerSocket for TCP connections
+     */
+    public class DefaultServerSocketFactory implements ServerSocketFactory {
+
+        @Override
+        public ServerSocket create() {
+            try {
+                return new ServerSocket();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            return myServerSocket;
+        }
+
+    }
+
+    /**
+     * Creates a new SSLServerSocket
+     */
+    public class SecureServerSocketFactory implements ServerSocketFactory {
+
+        private SSLServerSocketFactory sslServerSocketFactory;
+
+        private String[] sslProtocols;
+
+        public SecureServerSocketFactory(SSLServerSocketFactory sslServerSocketFactory, String[] sslProtocols) {
+            this.sslServerSocketFactory = sslServerSocketFactory;
+            this.sslProtocols = sslProtocols;
+        }
+
+        @Override
+        public ServerSocket create() {
+            SSLServerSocket ss = null;
+            try {
+                ss = (SSLServerSocket) this.sslServerSocketFactory.createServerSocket();
+                if (this.sslProtocols != null) {
+                    ss.setEnabledProtocols(this.sslProtocols);
+                } else {
+                    ss.setEnabledProtocols(ss.getSupportedProtocols());
+                }
+                ss.setUseClientMode(false);
+                ss.setWantClientAuth(false);
+                ss.setNeedClientAuth(false);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            return ss;
+        }
+
+    }
+
     private static final String CONTENT_DISPOSITION_REGEX = "([ |\t]*Content-Disposition[ |\t]*:)(.*)";
 
     private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile(CONTENT_DISPOSITION_REGEX, Pattern.CASE_INSENSITIVE);
@@ -826,10 +878,17 @@
          */
         private int findHeaderEnd(final byte[] buf, int rlen) {
             int splitbyte = 0;
-            while (splitbyte + 3 < rlen) {
-                if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') {
+            while (splitbyte + 1 < rlen) {
+
+                // RFC2616
+                if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && splitbyte + 3 < rlen && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') {
                     return splitbyte + 4;
                 }
+
+                // tolerance
+                if (buf[splitbyte] == '\n' && buf[splitbyte + 1] == '\n') {
+                    return splitbyte + 2;
+                }
                 splitbyte++;
             }
             return 0;
@@ -1527,11 +1586,11 @@
      */
     public interface TempFile {
 
-        void delete() throws Exception;
+        public void delete() throws Exception;
 
-        String getName();
+        public String getName();
 
-        OutputStream open() throws Exception;
+        public OutputStream open() throws Exception;
     }
 
     /**
@@ -1546,7 +1605,7 @@
 
         void clear();
 
-        TempFile createTempFile(String filename_hint) throws Exception;
+        public TempFile createTempFile(String filename_hint) throws Exception;
     }
 
     /**
@@ -1554,7 +1613,16 @@
      */
     public interface TempFileManagerFactory {
 
-        TempFileManager create();
+        public TempFileManager create();
+    }
+
+    /**
+     * Factory to create ServerSocketFactories.
+     */
+    public interface ServerSocketFactory {
+
+        public ServerSocket create();
+
     }
 
     /**
@@ -1713,9 +1781,7 @@
 
     private volatile ServerSocket myServerSocket;
 
-    private SSLServerSocketFactory sslServerSocketFactory;
-
-    private String[] sslProtocols;
+    private ServerSocketFactory serverSocketFactory = new DefaultServerSocketFactory();
 
     private Thread myThread;
 
@@ -1870,12 +1936,27 @@
         return wasStarted() && !this.myServerSocket.isClosed() && this.myThread.isAlive();
     }
 
+    public ServerSocketFactory getServerSocketFactory() {
+        return serverSocketFactory;
+    }
+
+    public void setServerSocketFactory(ServerSocketFactory serverSocketFactory) {
+        this.serverSocketFactory = serverSocketFactory;
+    }
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    public TempFileManagerFactory getTempFileManagerFactory() {
+        return tempFileManagerFactory;
+    }
+
     /**
      * Call before start() to serve over HTTPS instead of HTTP
      */
     public void makeSecure(SSLServerSocketFactory sslServerSocketFactory, String[] sslProtocols) {
-        this.sslServerSocketFactory = sslServerSocketFactory;
-        this.sslProtocols = sslProtocols;
+        this.serverSocketFactory = new SecureServerSocketFactory(sslServerSocketFactory, sslProtocols);
     }
 
     /**
@@ -1999,6 +2080,13 @@
     }
 
     /**
+     * Starts the server (in setDaemon(true) mode).
+     */
+    public void start(final int timeout) throws IOException {
+        start(timeout, true);
+    }
+
+    /**
      * Start the server.
      * 
      * @param timeout
@@ -2009,21 +2097,7 @@
      *             if the socket is in use.
      */
     public void start(final int timeout, boolean daemon) throws IOException {
-        if (this.sslServerSocketFactory != null) {
-            SSLServerSocket ss = (SSLServerSocket) this.sslServerSocketFactory.createServerSocket();
-            if (this.sslProtocols != null) {
-                ss.setEnabledProtocols(this.sslProtocols);
-            } else {
-                ss.setEnabledProtocols(ss.getSupportedProtocols());
-            }
-            ss.setUseClientMode(false);
-            ss.setWantClientAuth(false);
-            ss.setNeedClientAuth(false);
-            ss.setSoTimeout(timeout);
-            this.myServerSocket = ss;
-        } else {
-            this.myServerSocket = new ServerSocket();
-        }
+        this.myServerSocket = this.getServerSocketFactory().create();
         this.myServerSocket.setReuseAddress(true);
 
         ServerRunnable serverRunnable = createServerRunnable(timeout);
@@ -2046,13 +2120,6 @@
     }
 
     /**
-     * Starts the server (in setDaemon(true) mode).
-     */
-    public void start(final int timeout) throws IOException {
-        start(timeout, true);
-    }
-
-    /**
      * Stop the server.
      */
     public void stop() {
diff --git a/core/src/test/java/fi/iki/elonen/ServerSocketFactoryTest.java b/core/src/test/java/fi/iki/elonen/ServerSocketFactoryTest.java
new file mode 100644
index 0000000..17b6913
--- /dev/null
+++ b/core/src/test/java/fi/iki/elonen/ServerSocketFactoryTest.java
@@ -0,0 +1,75 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * NanoHttpd-Core
+ * %%
+ * Copyright (C) 2012 - 2015 nanohttpd
+ * %%
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 
+ * 3. Neither the name of the nanohttpd nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+import java.io.IOException;
+import java.net.ServerSocket;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ServerSocketFactoryTest extends NanoHTTPD {
+
+    public static final int PORT = 8192;
+
+    public ServerSocketFactoryTest() {
+        super(PORT);
+
+        this.setServerSocketFactory(new TestFactory());
+    }
+
+    @Test
+    public void isCustomServerSocketFactory() {
+        Assert.assertTrue(this.getServerSocketFactory() instanceof TestFactory);
+    }
+
+    @Test
+    public void testCreateServerSocket() {
+        ServerSocket ss = this.getServerSocketFactory().create();
+        Assert.assertTrue(ss != null);
+    }
+
+    private class TestFactory implements ServerSocketFactory {
+
+        @Override
+        public ServerSocket create() {
+            try {
+                return new ServerSocket();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            return null;
+        }
+    }
+}