diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java index 6699c674486..9903f295be7 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java @@ -262,11 +262,14 @@ public class Server extends HandlerWrapper implements Attributes @Override protected void doStart() throws Exception { - if (getStopAtShutdown()) + if (getStopAtShutdown()) { ShutdownThread.register(this); + ShutdownMonitor.getInstance(); // initialize + } LOG.info("jetty-"+__version); HttpGenerator.setServerVersion(__version); + MultiException mex=new MultiException(); if (_threadPool==null) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java new file mode 100644 index 00000000000..25335475957 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java @@ -0,0 +1,235 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Properties; + +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.thread.ShutdownThread; + +/** + * Shutdown/Stop Monitor thread. + *
+ * This thread listens on the port specified by the STOP.PORT system parameter (defaults to -1 for not listening) for + * request authenticated with the key given by the STOP.KEY system parameter (defaults to "eclipse") for admin requests. + *
+ * If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout. + *
+ * Commands "stop" and "status" are currently supported.
+ */
+public class ShutdownMonitor extends Thread
+{
+ private static final ShutdownMonitor INSTANCE = new ShutdownMonitor();
+
+ public static ShutdownMonitor getInstance()
+ {
+ return INSTANCE;
+ }
+
+ private final boolean DEBUG;
+ private final int port;
+ private final String key;
+ private final ServerSocket serverSocket;
+
+ private ShutdownMonitor()
+ {
+ Properties props = System.getProperties();
+
+ // Use the same debug option as /jetty-start/
+ this.DEBUG = props.containsKey("DEBUG");
+
+ // Use values passed thru /jetty-start/
+ int stopPort = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
+ String stopKey = props.getProperty("STOP.KEY",null);
+
+ ServerSocket sock = null;
+
+ try
+ {
+ if (stopPort < 0)
+ {
+ System.out.println("ShutdownMonitor not in use");
+ sock = null;
+ return;
+ }
+
+ setDaemon(true);
+ setName("ShutdownMonitor");
+
+ sock = new ServerSocket(stopPort,1,InetAddress.getByName("127.0.0.1"));
+ if (stopPort == 0)
+ {
+ // server assigned port in use
+ stopPort = sock.getLocalPort();
+ System.out.printf("STOP.PORT=%d%n",stopPort);
+ }
+
+ if (stopKey == null)
+ {
+ // create random key
+ stopKey = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36);
+ System.out.printf("STOP.KEY=%s%n",stopKey);
+ }
+
+ }
+ catch (Exception e)
+ {
+ debug(e);
+ System.err.println("Error binding monitor port " + stopPort + ": " + e.toString());
+ }
+ finally
+ {
+ // establish the port and key that are in use
+ this.port = stopPort;
+ this.key = stopKey;
+
+ this.serverSocket = sock;
+ debug("STOP.PORT=%d", port);
+ debug("STOP.KEY=%s", key);
+ debug("%s", serverSocket);
+ }
+
+ this.start();
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[port=%d]",this.getClass().getName(),port);
+ }
+
+ private void debug(Throwable t)
+ {
+ if (DEBUG)
+ {
+ t.printStackTrace(System.err);
+ }
+ }
+
+ private void debug(String format, Object... args)
+ {
+ if (DEBUG)
+ {
+ System.err.printf("[ShutdownMonitor] " + format + "%n",args);
+ }
+ }
+
+ @Override
+ public void run()
+ {
+ while (true)
+ {
+ Socket socket = null;
+ try
+ {
+ socket = serverSocket.accept();
+
+ LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
+ String key = lin.readLine();
+ if (!this.key.equals(key))
+ {
+ System.err.println("Ignoring command with incorrect key");
+ continue;
+ }
+
+ OutputStream out = socket.getOutputStream();
+
+ String cmd = lin.readLine();
+ debug("command=%s",cmd);
+ if ("stop".equals(cmd))
+ {
+ // Graceful Shutdown
+ debug("Issuing graceful shutdown..");
+ ShutdownThread.getInstance().run();
+
+ // Reply to client
+ debug("Informing client that we are stopped.");
+ out.write("Stopped\r\n".getBytes(StringUtil.__UTF8));
+ out.flush();
+
+ // Shutdown Monitor
+ debug("Shutting down monitor");
+ close(socket);
+ close(serverSocket);
+
+ // Kill JVM
+ debug("Killing JVM");
+ System.exit(0);
+ }
+ else if ("status".equals(cmd))
+ {
+ // Reply to client
+ out.write("OK\r\n".getBytes(StringUtil.__UTF8));
+ out.flush();
+ }
+ }
+ catch (Exception e)
+ {
+ debug(e);
+ System.err.println(e.toString());
+ }
+ finally
+ {
+ close(socket);
+ socket = null;
+ }
+ }
+ }
+
+ private void close(Socket socket)
+ {
+ if (socket == null)
+ {
+ return;
+ }
+
+ try
+ {
+ socket.close();
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ }
+
+ private void close(ServerSocket server)
+ {
+ if (server == null)
+ {
+ return;
+ }
+
+ try
+ {
+ server.close();
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ }
+}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
index 5ee0dead19e..18070aba51d 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
@@ -56,7 +56,8 @@ import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.MultiPartInputStream;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/**
@@ -84,6 +85,7 @@ import org.eclipse.jetty.util.TypeUtil;
*/
public class MultiPartFilter implements Filter
{
+ private static final Logger LOG = Log.getLogger(MultiPartFilter.class);
public final static String CONTENT_TYPE_SUFFIX=".org.eclipse.jetty.servlet.contentType";
private final static String MULTIPART = "org.eclipse.jetty.servlet.MultiPartFile.multiPartInputStream";
private File tempdir;
@@ -215,12 +217,7 @@ public class MultiPartFilter implements Filter
request.removeAttribute(MULTIPART);
}
- /* ------------------------------------------------------------ */
- private String value(String nameEqualsValue)
- {
- return nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim();
- }
-
+
/* ------------------------------------------------------------------------------- */
/**
* @see javax.servlet.Filter#destroy()
@@ -291,11 +288,12 @@ public class MultiPartFilter implements Filter
@Override
public Map getParameterMap()
{
- Map
- * Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It allows an application to be started with
- * the command "java -jar start.jar".
+ * Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It
+ * allows an application to be started with the command "java -jar start.jar".
*
- * The behaviour of Main is controlled by the parsing of the {@link Config} "org/eclipse/start/start.config" file obtained as a resource or file.
+ * The behaviour of Main is controlled by the parsing of the {@link Config} "org/eclipse/start/start.config" file
+ * obtained as a resource or file.
*
- * If the stop port is set to zero, then a random port is assigned and the port number
- * is printed to stdout.
- *
- * Commands "stop" and * "status" are currently supported.
- *
- */
-public class Monitor extends Thread
-{
- private Process _process;
- private final int _port;
- private final String _key;
-
- ServerSocket _socket;
-
- public Monitor(int port,String key)
- {
- try
- {
- if(port<0)
- return;
- setDaemon(true);
- setName("StopMonitor");
- _socket=new ServerSocket(port,1,InetAddress.getByName("127.0.0.1"));
- if (port==0)
- {
- port=_socket.getLocalPort();
- System.out.println(port);
- }
-
- if (key==null)
- {
- key=Long.toString((long)(Long.MAX_VALUE*Math.random()+this.hashCode()+System.currentTimeMillis()),36);
- System.out.println("STOP.KEY="+key);
- }
- }
- catch(Exception e)
- {
- Config.debug(e);
- System.err.println("Error binding monitor port "+port+": "+e.toString());
- }
- finally
- {
- _port=port;
- _key=key;
- }
-
- if (_socket!=null)
- this.start();
- else
- System.err.println("WARN: Not listening on monitor port: "+_port);
- }
-
- public Process getProcess()
- {
- return _process;
- }
-
- public void setProcess(Process process)
- {
- _process = process;
- }
-
- @Override
- public void run()
- {
- while (true)
- {
- Socket socket=null;
- try{
- socket=_socket.accept();
-
- LineNumberReader lin=
- new LineNumberReader(new InputStreamReader(socket.getInputStream()));
- String key=lin.readLine();
- if (!_key.equals(key))
- {
- System.err.println("Ignoring command with incorrect key");
- continue;
- }
-
- String cmd=lin.readLine();
- Config.debug("command=" + cmd);
- if ("stop".equals(cmd))
- {
- if (_process!=null)
- {
- //if we have a child process, wait for it to finish before we stop
- try
- {
- _process.destroy();
- _process.waitFor();
-
- }
- catch (InterruptedException e)
- {
- System.err.println("Interrupted waiting for child to terminate");
- }
- }
- socket.getOutputStream().write("Stopped\r\n".getBytes());
- try {socket.close();}catch(Exception e){e.printStackTrace();}
- try {_socket.close();}catch(Exception e){e.printStackTrace();}
- System.exit(0);
- }
- else if ("status".equals(cmd))
- {
- socket.getOutputStream().write("OK\r\n".getBytes());
- socket.getOutputStream().flush();
- }
- }
- catch(Exception e)
- {
- Config.debug(e);
- System.err.println(e.toString());
- }
- finally
- {
- if (socket!=null)
- {
- try{socket.close();}catch(Exception e){}
- }
- socket=null;
- }
- }
- }
-
-}
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java
index 4da3df3ee79..ba0f98bf1dd 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java
@@ -532,7 +532,7 @@ public class MultiPartInputStream
throw new IOException("Missing content-disposition");
}
- QuotedStringTokenizer tok=new QuotedStringTokenizer(contentDisposition,";");
+ QuotedStringTokenizer tok=new QuotedStringTokenizer(contentDisposition,";", false, true);
String name=null;
String filename=null;
while(tok.hasMoreTokens())
@@ -544,7 +544,7 @@ public class MultiPartInputStream
else if(tl.startsWith("name="))
name=value(t, true);
else if(tl.startsWith("filename="))
- filename=value(t, false);
+ filename=filenameValue(t);
}
// Check disposition
@@ -712,6 +712,7 @@ public class MultiPartInputStream
/* ------------------------------------------------------------ */
private String value(String nameEqualsValue, boolean splitAfterSpace)
{
+ /*
String value=nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim();
int i=value.indexOf(';');
if(i>0)
@@ -727,6 +728,38 @@ public class MultiPartInputStream
value=value.substring(0,i);
}
return value;
+ */
+ int idx = nameEqualsValue.indexOf('=');
+ String value = nameEqualsValue.substring(idx+1).trim();
+ return QuotedStringTokenizer.unquoteOnly(value);
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private String filenameValue(String nameEqualsValue)
+ {
+ int idx = nameEqualsValue.indexOf('=');
+ String value = nameEqualsValue.substring(idx+1).trim();
+
+ if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*"))
+ {
+ //incorrectly escaped IE filenames that have the whole path
+ //we just strip any leading & trailing quotes and leave it as is
+ char first=value.charAt(0);
+ if (first=='"' || first=='\'')
+ value=value.substring(1);
+ char last=value.charAt(value.length()-1);
+ if (last=='"' || last=='\'')
+ value = value.substring(0,value.length()-1);
+
+ return value;
+ }
+ else
+ //unquote the string, but allow any backslashes that don't
+ //form a valid escape sequence to remain as many browsers
+ //even on *nix systems will not escape a filename containing
+ //backslashes
+ return QuotedStringTokenizer.unquoteOnly(value, true);
}
private static class Base64InputStream extends InputStream
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java
index 8dc0a816f8f..54ee74fe339 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java
@@ -409,12 +409,21 @@ public class QuotedStringTokenizer
}
}
+
+ /* ------------------------------------------------------------ */
+ public static String unquoteOnly(String s)
+ {
+ return unquoteOnly(s, false);
+ }
+
+
/* ------------------------------------------------------------ */
/** Unquote a string, NOT converting unicode sequences
* @param s The string to unquote.
+ * @param lenient if true, will leave in backslashes that aren't valid escapes
* @return quoted string
*/
- public static String unquoteOnly(String s)
+ public static String unquoteOnly(String s, boolean lenient)
{
if (s==null)
return null;
@@ -435,6 +444,10 @@ public class QuotedStringTokenizer
if (escape)
{
escape=false;
+ if (lenient && !isValidEscaping(c))
+ {
+ b.append('\\');
+ }
b.append(c);
}
else if (c=='\\')
@@ -449,13 +462,19 @@ public class QuotedStringTokenizer
return b.toString();
}
-
+
+ /* ------------------------------------------------------------ */
+ public static String unquote(String s)
+ {
+ return unquote(s,false);
+ }
+
/* ------------------------------------------------------------ */
/** Unquote a string.
* @param s The string to unquote.
* @return quoted string
*/
- public static String unquote(String s)
+ public static String unquote(String s, boolean lenient)
{
if (s==null)
return null;
@@ -512,6 +531,10 @@ public class QuotedStringTokenizer
);
break;
default:
+ if (lenient && !isValidEscaping(c))
+ {
+ b.append('\\');
+ }
b.append(c);
}
}
@@ -527,6 +550,20 @@ public class QuotedStringTokenizer
return b.toString();
}
+
+
+ /* ------------------------------------------------------------ */
+ /** Check that char c (which is preceded by a backslash) is a valid
+ * escape sequence.
+ * @param c
+ * @return
+ */
+ private static boolean isValidEscaping(char c)
+ {
+ return ((c == 'n') || (c == 'r') || (c == 't') ||
+ (c == 'f') || (c == 'b') || (c == '\\') ||
+ (c == '/') || (c == '"') || (c == 'u'));
+ }
/* ------------------------------------------------------------ */
/**
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
index 62a5ad3ca4b..664b2b1d7bf 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
@@ -434,6 +434,69 @@ public class MultiPartInputStreamTest extends TestCase
assertThat(baos.toString("UTF-8"), is("Other"));
}
+
+ public void testBadlyEncodedFilename() throws Exception
+ {
+
+ String contents = "--AaB03x\r\n"+
+ "content-disposition: form-data; name=\"stuff\"; filename=\"" +"Taken on Aug 22 \\ 2012.jpg" + "\"\r\n"+
+ "Content-Type: text/plain\r\n"+
+ "\r\n"+"stuff"+
+ "aaa"+"\r\n" +
+ "--AaB03x--\r\n";
+
+ MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
+ MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(contents.getBytes()),
+ _contentType,
+ config,
+ _tmpDir);
+ mpis.setDeleteOnExit(true);
+ Collection