Merge remote-tracking branch 'origin/jetty-7' into jetty-8
Conflicts: jetty-server/src/main/java/org/eclipse/jetty/server/Server.java jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
This commit is contained in:
commit
32e1d0f29c
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout.
|
||||
* <p>
|
||||
* 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 */
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,11 +217,6 @@ public class MultiPartFilter implements Filter
|
|||
request.removeAttribute(MULTIPART);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private String value(String nameEqualsValue)
|
||||
{
|
||||
return nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
/**
|
||||
|
@ -291,11 +288,12 @@ public class MultiPartFilter implements Filter
|
|||
@Override
|
||||
public Map getParameterMap()
|
||||
{
|
||||
Map<String, String> cmap = new HashMap<String,String>();
|
||||
Map<String, String[]> cmap = new HashMap<String,String[]>();
|
||||
|
||||
for ( Object key : _params.keySet() )
|
||||
{
|
||||
cmap.put((String)key,getParameter((String)key));
|
||||
String[] a = LazyList.toStringArray(getParameter((String)key));
|
||||
cmap.put((String)key,a);
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(cmap);
|
||||
|
|
|
@ -18,18 +18,19 @@
|
|||
|
||||
package org.eclipse.jetty.servlets;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.ServletException;
|
||||
|
@ -204,6 +205,115 @@ public class MultipartFilterTest
|
|||
assertTrue(response.getContent().indexOf("brown cow")>=0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadlyEncodedFilename() throws Exception
|
||||
{
|
||||
// generated and parsed test
|
||||
HttpTester request = new HttpTester();
|
||||
HttpTester response = new HttpTester();
|
||||
|
||||
// test GET
|
||||
request.setMethod("POST");
|
||||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setURI("/context/dump");
|
||||
|
||||
String boundary="XyXyXy";
|
||||
request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
|
||||
|
||||
|
||||
String content = "--" + boundary + "\r\n"+
|
||||
"Content-Disposition: form-data; name=\"fileup\"; filename=\"Taken on Aug 22 \\ 2012.jpg\"\r\n"+
|
||||
"Content-Type: application/octet-stream\r\n\r\n"+
|
||||
"How now brown cow."+
|
||||
"\r\n--" + boundary + "--\r\n\r\n";
|
||||
|
||||
request.setContent(content);
|
||||
|
||||
response.parse(tester.getResponses(request.generate()));
|
||||
|
||||
//System.out.printf("Content: [%s]%n", response.getContent());
|
||||
|
||||
assertThat(response.getMethod(), nullValue());
|
||||
assertThat(response.getStatus(), is(HttpServletResponse.SC_OK));
|
||||
|
||||
assertThat(response.getContent(), containsString("Filename [Taken on Aug 22 \\ 2012.jpg]"));
|
||||
assertThat(response.getContent(), containsString("How now brown cow."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadlyEncodedMSFilename() throws Exception
|
||||
{
|
||||
// generated and parsed test
|
||||
HttpTester request = new HttpTester();
|
||||
HttpTester response = new HttpTester();
|
||||
|
||||
// test GET
|
||||
request.setMethod("POST");
|
||||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setURI("/context/dump");
|
||||
|
||||
String boundary="XyXyXy";
|
||||
request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
|
||||
|
||||
|
||||
String content = "--" + boundary + "\r\n"+
|
||||
"Content-Disposition: form-data; name=\"fileup\"; filename=\"c:\\this\\really\\is\\some\\path\\to\\a\\file.txt\"\r\n"+
|
||||
"Content-Type: application/octet-stream\r\n\r\n"+
|
||||
"How now brown cow."+
|
||||
"\r\n--" + boundary + "--\r\n\r\n";
|
||||
|
||||
request.setContent(content);
|
||||
|
||||
response.parse(tester.getResponses(request.generate()));
|
||||
|
||||
//System.out.printf("Content: [%s]%n", response.getContent());
|
||||
|
||||
assertThat(response.getMethod(), nullValue());
|
||||
assertThat(response.getStatus(), is(HttpServletResponse.SC_OK));
|
||||
|
||||
assertThat(response.getContent(), containsString("Filename [c:\\this\\really\\is\\some\\path\\to\\a\\file.txt]"));
|
||||
assertThat(response.getContent(), containsString("How now brown cow."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectlyEncodedMSFilename() throws Exception
|
||||
{
|
||||
// generated and parsed test
|
||||
HttpTester request = new HttpTester();
|
||||
HttpTester response = new HttpTester();
|
||||
|
||||
// test GET
|
||||
request.setMethod("POST");
|
||||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setURI("/context/dump");
|
||||
|
||||
String boundary="XyXyXy";
|
||||
request.setHeader("Content-Type","multipart/form-data; boundary="+boundary);
|
||||
|
||||
|
||||
String content = "--" + boundary + "\r\n"+
|
||||
"Content-Disposition: form-data; name=\"fileup\"; filename=\"c:\\\\this\\\\really\\\\is\\\\some\\\\path\\\\to\\\\a\\\\file.txt\"\r\n"+
|
||||
"Content-Type: application/octet-stream\r\n\r\n"+
|
||||
"How now brown cow."+
|
||||
"\r\n--" + boundary + "--\r\n\r\n";
|
||||
|
||||
request.setContent(content);
|
||||
|
||||
response.parse(tester.getResponses(request.generate()));
|
||||
|
||||
//System.out.printf("Content: [%s]%n", response.getContent());
|
||||
|
||||
assertThat(response.getMethod(), nullValue());
|
||||
assertThat(response.getStatus(), is(HttpServletResponse.SC_OK));
|
||||
|
||||
assertThat(response.getContent(), containsString("Filename [c:\\this\\really\\is\\some\\path\\to\\a\\file.txt]"));
|
||||
assertThat(response.getContent(), containsString("How now brown cow."));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Test multipart with parts encoded in base64 (RFC1521 section 5)
|
||||
*/
|
||||
|
@ -555,14 +665,13 @@ public class MultipartFilterTest
|
|||
*/
|
||||
public static class TestServletParameterMap extends DumpServlet
|
||||
{
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
assertEquals("How now brown cow.", req.getParameterMap().get("strupContent-Type:"));
|
||||
String[] content = req.getParameterMap().get("\"strup\"Content-Type: application/octet-stream");
|
||||
assertThat (content[0], containsString("How now brown cow."));
|
||||
super.doPost(req, resp);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -618,8 +727,17 @@ public class MultipartFilterTest
|
|||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
resp.getWriter().println((IO.toString(new FileInputStream((File)req.getAttribute("fileup")))));
|
||||
}
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
File file = (File)req.getAttribute("fileup");
|
||||
in = new FileInputStream(file);
|
||||
|
||||
PrintWriter out = resp.getWriter();
|
||||
out.printf("Filename [%s]\r\n", req.getParameter("fileup"));
|
||||
out.println(IO.toString(in));
|
||||
} finally {
|
||||
IO.close(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,12 +53,13 @@ import java.util.Set;
|
|||
/*-------------------------------------------*/
|
||||
/**
|
||||
* <p>
|
||||
* 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".
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*/
|
||||
public class Main
|
||||
|
@ -492,11 +493,6 @@ public class Main
|
|||
/* ------------------------------------------------------------ */
|
||||
public void start(List<String> xmls) throws IOException, InterruptedException
|
||||
{
|
||||
// Setup Start / Stop Monitoring
|
||||
int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
|
||||
String key = Config.getProperty("STOP.KEY",null);
|
||||
Monitor monitor = new Monitor(port,key);
|
||||
|
||||
// Load potential Config (start.config)
|
||||
List<String> configuredXmls = loadConfig(xmls);
|
||||
|
||||
|
@ -581,9 +577,8 @@ public class Main
|
|||
copyInThread(process.getErrorStream(),System.err);
|
||||
copyInThread(process.getInputStream(),System.out);
|
||||
copyInThread(System.in,process.getOutputStream());
|
||||
monitor.setProcess(process);
|
||||
process.waitFor();
|
||||
|
||||
System.exit(0); // exit JVM when child process ends.
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -688,11 +683,18 @@ public class Main
|
|||
cmd.addArg(x);
|
||||
}
|
||||
cmd.addRawArg("-Djetty.home=" + _jettyHome);
|
||||
|
||||
// Special Stop/Shutdown properties
|
||||
ensureSystemPropertySet("STOP.PORT");
|
||||
ensureSystemPropertySet("STOP.KEY");
|
||||
|
||||
// System Properties
|
||||
for (String p : _sysProps)
|
||||
{
|
||||
String v = System.getProperty(p);
|
||||
cmd.addEqualsArg("-D" + p,v);
|
||||
}
|
||||
|
||||
cmd.addArg("-cp");
|
||||
cmd.addRawArg(classpath.toString());
|
||||
cmd.addRawArg(_config.getMainClassname());
|
||||
|
@ -715,6 +717,34 @@ public class Main
|
|||
return cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the System Properties are set (if defined as a System property, or start.config property, or
|
||||
* start.ini property)
|
||||
*
|
||||
* @param key
|
||||
* the key to be sure of
|
||||
*/
|
||||
private void ensureSystemPropertySet(String key)
|
||||
{
|
||||
if (_sysProps.contains(key))
|
||||
{
|
||||
return; // done
|
||||
}
|
||||
|
||||
Properties props = Config.getProperties();
|
||||
if (props.containsKey(key))
|
||||
{
|
||||
String val = props.getProperty(key,null);
|
||||
if (val == null)
|
||||
{
|
||||
return; // no value to set
|
||||
}
|
||||
// setup system property
|
||||
_sysProps.add(key);
|
||||
System.setProperty(key,val);
|
||||
}
|
||||
}
|
||||
|
||||
private String findJavaBin()
|
||||
{
|
||||
File javaHome = new File(System.getProperty("java.home"));
|
||||
|
@ -928,8 +958,8 @@ public class Main
|
|||
/**
|
||||
* Load Configuration.
|
||||
*
|
||||
* No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to execute the {@link Class} specified by
|
||||
* {@link Config#getMainClassname()} is executed.
|
||||
* No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to
|
||||
* execute the {@link Class} specified by {@link Config#getMainClassname()} is executed.
|
||||
*
|
||||
* @param xmls
|
||||
* the command line specified xml configuration options.
|
||||
|
@ -1010,7 +1040,6 @@ public class Main
|
|||
stop(port,key,0);
|
||||
}
|
||||
|
||||
|
||||
public void stop(int port, String key, int timeout)
|
||||
{
|
||||
int _port = port;
|
||||
|
@ -1040,11 +1069,15 @@ public class Main
|
|||
|
||||
if (timeout > 0)
|
||||
{
|
||||
System.err.println("Waiting"+(timeout > 0 ? (" "+timeout+"sec") : "")+" for jetty to stop");
|
||||
System.err.printf("Waiting %,d seconds for jetty to stop%n",timeout);
|
||||
LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
|
||||
String response=lin.readLine();
|
||||
String response;
|
||||
while ((response = lin.readLine()) != null)
|
||||
{
|
||||
Config.debug("Received \"" + response + "\"");
|
||||
if ("Stopped".equals(response))
|
||||
System.err.println("Stopped");
|
||||
System.err.println("Server reports itself as Stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
|
|
@ -1,158 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.start;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.LineNumberReader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
/*-------------------------------------------*/
|
||||
/** 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.
|
||||
* <p>
|
||||
* If the stop port is set to zero, then a random port is assigned and the port number
|
||||
* is printed to stdout.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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=='\\')
|
||||
|
@ -450,12 +463,18 @@ 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);
|
||||
}
|
||||
}
|
||||
|
@ -528,6 +551,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'));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return handle double quotes if true
|
||||
|
|
|
@ -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<Part> parts = mpis.getParts();
|
||||
assertThat(parts.size(), is(1));
|
||||
assertThat(((MultiPartInputStream.MultiPart)parts.iterator().next()).getContentDispositionFilename(), is("Taken on Aug 22 \\ 2012.jpg"));
|
||||
}
|
||||
|
||||
public void testBadlyEncodedMSFilename() throws Exception
|
||||
{
|
||||
|
||||
String contents = "--AaB03x\r\n"+
|
||||
"content-disposition: form-data; name=\"stuff\"; filename=\"" +"c:\\this\\really\\is\\some\\path\\to\\a\\file.txt" + "\"\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<Part> parts = mpis.getParts();
|
||||
assertThat(parts.size(), is(1));
|
||||
assertThat(((MultiPartInputStream.MultiPart)parts.iterator().next()).getContentDispositionFilename(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
|
||||
}
|
||||
|
||||
public void testCorrectlyEncodedMSFilename() throws Exception
|
||||
{
|
||||
String contents = "--AaB03x\r\n"+
|
||||
"content-disposition: form-data; name=\"stuff\"; filename=\"" +"c:\\\\this\\\\really\\\\is\\\\some\\\\path\\\\to\\\\a\\\\file.txt" + "\"\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<Part> parts = mpis.getParts();
|
||||
assertThat(parts.size(), is(1));
|
||||
assertThat(((MultiPartInputStream.MultiPart)parts.iterator().next()).getContentDispositionFilename(), is("c:\\this\\really\\is\\some\\path\\to\\a\\file.txt"));
|
||||
}
|
||||
|
||||
public void testMulti ()
|
||||
throws Exception
|
||||
{
|
||||
|
@ -445,6 +508,9 @@ public class MultiPartInputStreamTest extends TestCase
|
|||
testMulti("stuff with spaces.txt");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void testMulti(String filename) throws IOException, ServletException
|
||||
{
|
||||
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
|
||||
|
|
|
@ -18,9 +18,7 @@
|
|||
|
||||
package org.eclipse.jetty.util;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -190,4 +188,21 @@ public class QuotedStringTokenizerTest
|
|||
assertEquals("ba\\uXXXXaaa", QuotedStringTokenizer.unquoteOnly("\"ba\\\\uXXXXaaa\""));
|
||||
}
|
||||
|
||||
/**
|
||||
* When encountering a Content-Disposition line during a multi-part mime file
|
||||
* upload, the filename="..." field can contain '\' characters that do not
|
||||
* belong to a proper escaping sequence, this tests QuotedStringTokenizer to
|
||||
* ensure that it preserves those slashes for where they cannot be escaped.
|
||||
*/
|
||||
@Test
|
||||
public void testNextTokenOnContentDisposition()
|
||||
{
|
||||
String content_disposition = "form-data; name=\"fileup\"; filename=\"Taken on Aug 22 \\ 2012.jpg\"";
|
||||
|
||||
QuotedStringTokenizer tok=new QuotedStringTokenizer(content_disposition,";",false,true);
|
||||
|
||||
assertEquals("form-data", tok.nextToken().trim());
|
||||
assertEquals("name=\"fileup\"", tok.nextToken().trim());
|
||||
assertEquals("filename=\"Taken on Aug 22 \\ 2012.jpg\"", tok.nextToken().trim());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,6 +100,12 @@ public class ServletTester
|
|||
_server.start();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void join() throws Exception
|
||||
{
|
||||
_server.join();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void stop() throws Exception
|
||||
{
|
||||
|
|
|
@ -128,6 +128,7 @@ public class CookieDump extends HttpServlet
|
|||
// For testing --stop with STOP.WAIT handling of the jetty-start behavior.
|
||||
if (Boolean.getBoolean("test.slow.destroy"))
|
||||
{
|
||||
log("Simulating a slow destroy (10 seconds)",null);
|
||||
try
|
||||
{
|
||||
TimeUnit.SECONDS.sleep(10);
|
||||
|
|
Loading…
Reference in New Issue