NanoHTTPD.java revision dfea30a36949bc24a698ac39d8a714fc8f1c9e82
1269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkepackage fi.iki.elonen;
2269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
39788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawkeimport java.io.*;
4f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawkeimport java.net.InetSocketAddress;
5269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.net.ServerSocket;
6269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.net.Socket;
7dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawkeimport java.nio.ByteBuffer;
8dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawkeimport java.nio.channels.FileChannel;
97b88819e82b89ac3476ce060903468076a73de8fPaul Hawkeimport java.text.SimpleDateFormat;
109788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawkeimport java.util.*;
11269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
12269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke/**
13269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * A simple, tiny, nicely embeddable HTTP 1.0 (partially 1.1) server in Java
14d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
15d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
162b54eda5fee8430268a99d73b061e790362db544Tom Hermann * NanoHTTPD version 1.25, Copyright &copy; 2001,2005-2012 Jarno Elonen (elonen@iki.fi, http://iki.fi/elonen/) and Copyright &copy; 2010
172b54eda5fee8430268a99d73b061e790362db544Tom Hermann * Konstantinos Togias (info@ktogias.gr, http://ktogias.gr)
1801ecef8887ed32be34ef7442a656a0b3330ecb8dPaul Hawke * <p/>
1901ecef8887ed32be34ef7442a656a0b3330ecb8dPaul Hawke * Uplifted to Java5 by Micah Hainline and Paul Hawke (paul.hawke@gmail.com).
20d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
21d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
22269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <b>Features + limitations: </b>
23269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <ul>
24d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
25269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Only one Java file</li>
2601ecef8887ed32be34ef7442a656a0b3330ecb8dPaul Hawke * <li>Java 5 compatible</li>
27269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Released as open source, Modified BSD licence</li>
28269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>No fixed config files, logging, authorization etc. (Implement yourself if you need them.)</li>
29269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports parameter parsing of GET and POST methods (+ rudimentary PUT support in 1.25)</li>
30269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports both dynamic content and file serving</li>
31269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports file upload (since version 1.2, 2010)</li>
32269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports partial content (streaming)</li>
33269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports ETags</li>
34269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Never caches anything</li>
35269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Doesn't limit bandwidth, request time or simultaneous connections</li>
36269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Default code serves files and shows all HTTP parameters and headers</li>
37269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server supports directory listing, index.html and index.htm</li>
38269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server supports partial content (streaming)</li>
39269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server supports ETags</li>
40269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server does the 301 redirection trick for directories without '/'</li>
41269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server supports simple skipping for files (continue download)</li>
42269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server serves also very long files without memory overhead</li>
43269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Contains a built-in list of most common mime types</li>
44269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>All header names are converted lowercase so they don't vary between browsers/clients</li>
45d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
46269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * </ul>
47d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
48d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
4901ecef8887ed32be34ef7442a656a0b3330ecb8dPaul Hawke * <b>How to use: </b>
50269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <ul>
51d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
5201ecef8887ed32be34ef7442a656a0b3330ecb8dPaul Hawke * <li>Subclass and implement serve() and embed to your own program</li>
53d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
54269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * </ul>
55d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
56269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * See the end of the source file for distribution license (Modified BSD licence)
57269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke */
58d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawkepublic abstract class NanoHTTPD {
59f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke    /*
60f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke     * Pseudo-Parameter to use to store the actual query string in the parameters map for later re-processing.
61f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke     */
62f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke    public static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING";
63269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    /**
64269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     * Common mime types for dynamic content
65269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     */
66269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    public static final String MIME_PLAINTEXT = "text/plain";
67269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    public static final String MIME_HTML = "text/html";
68269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    public static final String MIME_DEFAULT_BINARY = "application/octet-stream";
69f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawke    private final String hostname;
707b88819e82b89ac3476ce060903468076a73de8fPaul Hawke    private final int myPort;
717b88819e82b89ac3476ce060903468076a73de8fPaul Hawke    private ServerSocket myServerSocket;
727b88819e82b89ac3476ce060903468076a73de8fPaul Hawke    private Thread myThread;
734e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke    private TempFileManagerFactory tempFileManagerFactory;
744e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke    private AsyncRunner asyncRunner;
75269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
76269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    /**
77d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * Constructs an HTTP server on given port.
78269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     */
79df674ca7f11cee6336b66271c50476f389e0ff4bPaul Hawke    public NanoHTTPD(int port) {
80f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawke        this(null, port);
81f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawke    }
82f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawke
83f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawke    public NanoHTTPD(String hostname, int port) {
84f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawke        this.hostname = hostname;
857b88819e82b89ac3476ce060903468076a73de8fPaul Hawke        this.myPort = port;
864e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        this.tempFileManagerFactory = new DefaultTempFileManagerFactory();
874e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        this.asyncRunner = new DefaultAsyncRunner();
883f2aa2e2e0e02fe7e0151eb64d31265b578885ecPaul Hawke    }
893f2aa2e2e0e02fe7e0151eb64d31265b578885ecPaul Hawke
903f2aa2e2e0e02fe7e0151eb64d31265b578885ecPaul Hawke    /**
913f2aa2e2e0e02fe7e0151eb64d31265b578885ecPaul Hawke     * Starts the server
92d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * <p/>
93d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * Throws an IOException if the socket is already in use
943f2aa2e2e0e02fe7e0151eb64d31265b578885ecPaul Hawke     */
957b88819e82b89ac3476ce060903468076a73de8fPaul Hawke    public void start() throws IOException {
96f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawke        myServerSocket = new ServerSocket();
97f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawke        myServerSocket.bind((hostname != null) ? new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort));
98f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawke
99269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        myThread = new Thread(new Runnable() {
100269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            @Override
101269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            public void run() {
1026200253df7b934c807eb3436b51a3b039406457dPaul Hawke                do {
1036200253df7b934c807eb3436b51a3b039406457dPaul Hawke                    try {
1046200253df7b934c807eb3436b51a3b039406457dPaul Hawke                        final Socket finalAccept = myServerSocket.accept();
1056200253df7b934c807eb3436b51a3b039406457dPaul Hawke                        InputStream inputStream = finalAccept.getInputStream();
1066200253df7b934c807eb3436b51a3b039406457dPaul Hawke                        OutputStream outputStream = finalAccept.getOutputStream();
1076200253df7b934c807eb3436b51a3b039406457dPaul Hawke                        TempFileManager tempFileManager = tempFileManagerFactory.create();
1086200253df7b934c807eb3436b51a3b039406457dPaul Hawke                        final HTTPSession session = new HTTPSession(tempFileManager, inputStream, outputStream);
1096200253df7b934c807eb3436b51a3b039406457dPaul Hawke                        asyncRunner.exec(new Runnable() {
1106200253df7b934c807eb3436b51a3b039406457dPaul Hawke                            @Override
1116200253df7b934c807eb3436b51a3b039406457dPaul Hawke                            public void run() {
1126200253df7b934c807eb3436b51a3b039406457dPaul Hawke                                session.run();
1136200253df7b934c807eb3436b51a3b039406457dPaul Hawke                                if (finalAccept != null) {
1146200253df7b934c807eb3436b51a3b039406457dPaul Hawke                                    try {
1156200253df7b934c807eb3436b51a3b039406457dPaul Hawke                                        finalAccept.close();
1166200253df7b934c807eb3436b51a3b039406457dPaul Hawke                                    } catch (IOException ignored) {
1176200253df7b934c807eb3436b51a3b039406457dPaul Hawke                                    }
1186200253df7b934c807eb3436b51a3b039406457dPaul Hawke                                }
1196200253df7b934c807eb3436b51a3b039406457dPaul Hawke                            }
1206200253df7b934c807eb3436b51a3b039406457dPaul Hawke                        });
1216200253df7b934c807eb3436b51a3b039406457dPaul Hawke                    } catch (IOException e) {
1226200253df7b934c807eb3436b51a3b039406457dPaul Hawke                    }
1236200253df7b934c807eb3436b51a3b039406457dPaul Hawke                } while (true);
124269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
125269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        });
126269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        myThread.setDaemon(true);
127269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        myThread.start();
128269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    }
129d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
130269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    /**
131269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     * Stops the server.
132269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     */
133269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    public void stop() {
134269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        try {
135269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            myServerSocket.close();
136269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            myThread.join();
137269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        } catch (IOException ioe) {
1387b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            ioe.printStackTrace();
139269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        } catch (InterruptedException e) {
1407b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            e.printStackTrace();
141269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
142269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    }
143269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
1444e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke    public void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory) {
1454e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        this.tempFileManagerFactory = tempFileManagerFactory;
1464e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke    }
1474e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke
1484e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke    public void setAsyncRunner(AsyncRunner asyncRunner) {
1494e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        this.asyncRunner = asyncRunner;
1504e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke    }
1514e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke
152a6619fe65dd3a997960724faf7c51685de793f91Paul Hawke    /**
153d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * Override this to customize the server.
154d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * <p/>
155d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * <p/>
156d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * (By default, this delegates to serveFile() and allows directory listing.)
1579788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke     *
1589788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke     * @param uri    Percent-decoded URI without parameters, for example "/index.cgi"
1599788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke     * @param method "GET", "POST" etc.
1609788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke     * @param parms  Parsed, percent decoded parameters from URI and, in case of POST, data.
1619788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke     * @param header Header entries, percent decoded
162d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * @return HTTP response, see class Response for details
163269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     */
1642b54eda5fee8430268a99d73b061e790362db544Tom Hermann    public abstract Response serve(String uri, Method method, Map<String, String> header, Map<String, String> parms,
1659788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                                   Map<String, String> files);
1669788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke
167f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke    /**
168f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke     * Decodes the percent encoding scheme. <br/>
169f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke     * For example: "an+example%20string" -> "an example string"
170f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke     */
171bcb535ea1fc2167a1e55ee5a6d8a0e7492b81605Paul Hawke    protected String decodePercent(String str) throws InterruptedException {
172f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke        try {
173f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            StringBuilder sb = new StringBuilder();
174f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            for (int i = 0; i < str.length(); i++) {
175f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                char c = str.charAt(i);
176f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                switch (c) {
177f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    case '+':
178f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                        sb.append(' ');
179f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                        break;
180f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    case '%':
181f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                        sb.append((char) Integer.parseInt(str.substring(i + 1, i + 3), 16));
182f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                        i += 2;
183f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                        break;
184f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    default:
185f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                        sb.append(c);
186f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                        break;
187f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                }
188f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            }
189f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            return sb.toString();
190f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke        } catch (Exception e) {
191f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            throw new InterruptedException();
192f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke        }
193f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke    }
194f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawke
195f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke    protected Map<String, List<String>> decodeParameters(Map<String, String> parms) {
196f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke        return this.decodeParameters(parms.get(QUERY_STRING_PARAMETER));
197f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke    }
198f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke
199f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke    protected Map<String, List<String>> decodeParameters(String queryString) {
200f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke        Map<String, List<String>> parms = new HashMap<String, List<String>>();
201f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke        if (queryString != null) {
202f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            StringTokenizer st = new StringTokenizer(queryString, "&");
203f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            while (st.hasMoreTokens()) {
204f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                String e = st.nextToken();
205f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                int sep = e.indexOf('=');
206f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                try {
207f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    String propertyName = (sep >= 0) ? decodePercent(e.substring(0, sep)).trim() : decodePercent(e).trim();
208f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    if (!parms.containsKey(propertyName)) {
209f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                        parms.put(propertyName, new ArrayList<String>());
210f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    }
211f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    String propertyValue = (sep >= 0) ? decodePercent(e.substring(sep + 1)) : null;
212f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    if (propertyValue != null) {
213f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                        parms.get(propertyName).add(propertyValue);
214f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    }
215f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                } catch (InterruptedException e1) {
216f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    e1.printStackTrace();
217f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                }
218f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            }
219f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke        }
220f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke        return parms;
221f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke    }
222f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke
2239788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke    public enum Method {
2249788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        GET, PUT, POST, DELETE;
2259788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke
2269788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        static Method lookup(String method) {
2279788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            for (Method m : Method.values()) {
2289788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                if (m.toString().equalsIgnoreCase(method)) {
2299788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                    return m;
2309788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                }
2319788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            }
2329788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            return null;
2339788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        }
2349788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke    }
2359788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke
2366200253df7b934c807eb3436b51a3b039406457dPaul Hawke    public interface AsyncRunner {
2376200253df7b934c807eb3436b51a3b039406457dPaul Hawke        void exec(Runnable code);
2386200253df7b934c807eb3436b51a3b039406457dPaul Hawke    }
2396200253df7b934c807eb3436b51a3b039406457dPaul Hawke
2406200253df7b934c807eb3436b51a3b039406457dPaul Hawke    public interface TempFileManagerFactory {
2416200253df7b934c807eb3436b51a3b039406457dPaul Hawke        TempFileManager create();
2426200253df7b934c807eb3436b51a3b039406457dPaul Hawke    }
2436200253df7b934c807eb3436b51a3b039406457dPaul Hawke
2446200253df7b934c807eb3436b51a3b039406457dPaul Hawke    public interface TempFileManager {
2456200253df7b934c807eb3436b51a3b039406457dPaul Hawke        TempFile createTempFile() throws Exception;
2466200253df7b934c807eb3436b51a3b039406457dPaul Hawke
2476200253df7b934c807eb3436b51a3b039406457dPaul Hawke        void clear();
2486200253df7b934c807eb3436b51a3b039406457dPaul Hawke    }
2496200253df7b934c807eb3436b51a3b039406457dPaul Hawke
2506200253df7b934c807eb3436b51a3b039406457dPaul Hawke    public interface TempFile {
2516200253df7b934c807eb3436b51a3b039406457dPaul Hawke        OutputStream open() throws Exception;
2526200253df7b934c807eb3436b51a3b039406457dPaul Hawke
2536200253df7b934c807eb3436b51a3b039406457dPaul Hawke        void delete() throws Exception;
2546200253df7b934c807eb3436b51a3b039406457dPaul Hawke
2556200253df7b934c807eb3436b51a3b039406457dPaul Hawke        String getName();
2566200253df7b934c807eb3436b51a3b039406457dPaul Hawke    }
2576200253df7b934c807eb3436b51a3b039406457dPaul Hawke
2589788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke    /**
2599788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke     * HTTP response. Return one of these from serve().
2609788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke     */
2619788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke    public static class Response {
2629788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        /**
2639788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         * HTTP status code after processing, e.g. "200 OK", HTTP_OK
2649788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         */
2659788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        public Status status;
2669788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        /**
2679788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         * MIME type of content, e.g. "text/html"
2689788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         */
2699788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        public String mimeType;
2709788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        /**
2719788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         * Data of the response, may be null.
2729788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         */
2739788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        public InputStream data;
2749788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        /**
2759788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         * Headers for the HTTP response. Use addHeader() to add lines.
2769788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         */
2779788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        public Map<String, String> header = new HashMap<String, String>();
2789788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke
2799788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        /**
2809788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         * Default constructor: response = HTTP_OK, mime = MIME_HTML and your supplied message
2819788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         */
2829788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        public Response(String msg) {
2839788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            this(Status.OK, MIME_HTML, msg);
2849788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        }
2859788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke
2869788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        /**
2879788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         * Basic constructor.
2889788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         */
2899788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        public Response(Status status, String mimeType, InputStream data) {
2909788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            this.status = status;
2919788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            this.mimeType = mimeType;
2929788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            this.data = data;
2939788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        }
2949788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke
2959788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        /**
2969788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         * Convenience method that makes an InputStream out of given text.
2979788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         */
2989788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        public Response(Status status, String mimeType, String txt) {
2999788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            this.status = status;
3009788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            this.mimeType = mimeType;
3019788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            try {
3029788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                this.data = new ByteArrayInputStream(txt.getBytes("UTF-8"));
3039788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            } catch (java.io.UnsupportedEncodingException uee) {
3049788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                uee.printStackTrace();
3059788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            }
3069788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        }
3079788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke
308f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke        public static void error(OutputStream outputStream, Status error, String message) {
309f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            new Response(error, MIME_PLAINTEXT, message).send(outputStream);
310f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke        }
311f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke
3129788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        /**
3139788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         * Adds given line to the header.
3149788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         */
3159788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        public void addHeader(String name, String value) {
3169788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            header.put(name, value);
3179788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        }
3189788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke
3199788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        /**
3207f0727787957c2f093412c01d165846842ec2425Paul Hawke         * Sends given response to the socket.
3217f0727787957c2f093412c01d165846842ec2425Paul Hawke         */
3227f0727787957c2f093412c01d165846842ec2425Paul Hawke        private void send(OutputStream outputStream) {
3237f0727787957c2f093412c01d165846842ec2425Paul Hawke            String mime = mimeType;
3247f0727787957c2f093412c01d165846842ec2425Paul Hawke            SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
3257f0727787957c2f093412c01d165846842ec2425Paul Hawke            gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));
3267f0727787957c2f093412c01d165846842ec2425Paul Hawke
3277f0727787957c2f093412c01d165846842ec2425Paul Hawke            try {
3287f0727787957c2f093412c01d165846842ec2425Paul Hawke                if (status == null) {
3297f0727787957c2f093412c01d165846842ec2425Paul Hawke                    throw new Error("sendResponse(): Status can't be null.");
3307f0727787957c2f093412c01d165846842ec2425Paul Hawke                }
3317f0727787957c2f093412c01d165846842ec2425Paul Hawke                PrintWriter pw = new PrintWriter(outputStream);
3327f0727787957c2f093412c01d165846842ec2425Paul Hawke                pw.print("HTTP/1.0 " + status.getDescription() + " \r\n");
3337f0727787957c2f093412c01d165846842ec2425Paul Hawke
3347f0727787957c2f093412c01d165846842ec2425Paul Hawke                if (mime != null) {
3357f0727787957c2f093412c01d165846842ec2425Paul Hawke                    pw.print("Content-Type: " + mime + "\r\n");
3367f0727787957c2f093412c01d165846842ec2425Paul Hawke                }
3377f0727787957c2f093412c01d165846842ec2425Paul Hawke
3387f0727787957c2f093412c01d165846842ec2425Paul Hawke                if (header == null || header.get("Date") == null) {
3397f0727787957c2f093412c01d165846842ec2425Paul Hawke                    pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n");
3407f0727787957c2f093412c01d165846842ec2425Paul Hawke                }
3417f0727787957c2f093412c01d165846842ec2425Paul Hawke
3427f0727787957c2f093412c01d165846842ec2425Paul Hawke                if (header != null) {
3437f0727787957c2f093412c01d165846842ec2425Paul Hawke                    for (String key : header.keySet()) {
3447f0727787957c2f093412c01d165846842ec2425Paul Hawke                        String value = header.get(key);
3457f0727787957c2f093412c01d165846842ec2425Paul Hawke                        pw.print(key + ": " + value + "\r\n");
3467f0727787957c2f093412c01d165846842ec2425Paul Hawke                    }
3477f0727787957c2f093412c01d165846842ec2425Paul Hawke                }
3487f0727787957c2f093412c01d165846842ec2425Paul Hawke
3497f0727787957c2f093412c01d165846842ec2425Paul Hawke                pw.print("\r\n");
3507f0727787957c2f093412c01d165846842ec2425Paul Hawke                pw.flush();
3517f0727787957c2f093412c01d165846842ec2425Paul Hawke
3527f0727787957c2f093412c01d165846842ec2425Paul Hawke                if (data != null) {
3537f0727787957c2f093412c01d165846842ec2425Paul Hawke                    int pending = data.available(); // This is to support partial sends, see serveFile()
3547f0727787957c2f093412c01d165846842ec2425Paul Hawke                    int BUFFER_SIZE = 16 * 1024;
3557f0727787957c2f093412c01d165846842ec2425Paul Hawke                    byte[] buff = new byte[BUFFER_SIZE];
3567f0727787957c2f093412c01d165846842ec2425Paul Hawke                    while (pending > 0) {
3577f0727787957c2f093412c01d165846842ec2425Paul Hawke                        int read = data.read(buff, 0, ((pending > BUFFER_SIZE) ? BUFFER_SIZE : pending));
3587f0727787957c2f093412c01d165846842ec2425Paul Hawke                        if (read <= 0) {
3597f0727787957c2f093412c01d165846842ec2425Paul Hawke                            break;
3607f0727787957c2f093412c01d165846842ec2425Paul Hawke                        }
3617f0727787957c2f093412c01d165846842ec2425Paul Hawke                        outputStream.write(buff, 0, read);
3627f0727787957c2f093412c01d165846842ec2425Paul Hawke                        pending -= read;
3637f0727787957c2f093412c01d165846842ec2425Paul Hawke                    }
3647f0727787957c2f093412c01d165846842ec2425Paul Hawke                }
3657f0727787957c2f093412c01d165846842ec2425Paul Hawke                outputStream.flush();
3667f0727787957c2f093412c01d165846842ec2425Paul Hawke                outputStream.close();
3677f0727787957c2f093412c01d165846842ec2425Paul Hawke                if (data != null)
3687f0727787957c2f093412c01d165846842ec2425Paul Hawke                    data.close();
3697f0727787957c2f093412c01d165846842ec2425Paul Hawke            } catch (IOException ioe) {
3707f0727787957c2f093412c01d165846842ec2425Paul Hawke                // Couldn't write? No can do.
3717f0727787957c2f093412c01d165846842ec2425Paul Hawke            }
3727f0727787957c2f093412c01d165846842ec2425Paul Hawke        }
3737f0727787957c2f093412c01d165846842ec2425Paul Hawke
3747f0727787957c2f093412c01d165846842ec2425Paul Hawke        /**
3759788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         * Some HTTP response status codes
3769788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke         */
3779788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        public enum Status {
3789788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            OK(200, "OK"), CREATED(201, "Created"), NO_CONTENT(204, "No Content"), PARTIAL_CONTENT(206, "Partial Content"), REDIRECT(301,
3799788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                    "Moved Permanently"), NOT_MODIFIED(304, "Not Modified"), BAD_REQUEST(400, "Bad Request"), UNAUTHORIZED(401,
3809788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                    "Unauthorized"), FORBIDDEN(403, "Forbidden"), NOT_FOUND(404, "Not Found"), RANGE_NOT_SATISFIABLE(416,
3819788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                    "Requested Range Not Satisfiable"), INTERNAL_ERROR(500, "Internal Server Error");
3829788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            private int requestStatus;
3839788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            private String descr;
3849788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke
3859788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            Status(int requestStatus, String descr) {
3869788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                this.requestStatus = requestStatus;
3879788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                this.descr = descr;
3889788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            }
3899788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke
3909788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            public int getRequestStatus() {
3919788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                return this.requestStatus;
3929788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            }
3939788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke
3949788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            public String getDescription() {
3959788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                return "" + this.requestStatus + " " + descr;
3969788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke            }
3979788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke        }
3989788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke    }
399269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
4006200253df7b934c807eb3436b51a3b039406457dPaul Hawke    public static class DefaultTempFile implements TempFile {
4016200253df7b934c807eb3436b51a3b039406457dPaul Hawke        private File file;
4026200253df7b934c807eb3436b51a3b039406457dPaul Hawke        private OutputStream fstream;
4036200253df7b934c807eb3436b51a3b039406457dPaul Hawke
4046200253df7b934c807eb3436b51a3b039406457dPaul Hawke        public DefaultTempFile(String tempdir) throws IOException {
4056200253df7b934c807eb3436b51a3b039406457dPaul Hawke            file = File.createTempFile("NanoHTTPD-", "", new File(tempdir));
4066200253df7b934c807eb3436b51a3b039406457dPaul Hawke            fstream = new FileOutputStream(file);
4076200253df7b934c807eb3436b51a3b039406457dPaul Hawke        }
4086200253df7b934c807eb3436b51a3b039406457dPaul Hawke
4096200253df7b934c807eb3436b51a3b039406457dPaul Hawke        @Override
4106200253df7b934c807eb3436b51a3b039406457dPaul Hawke        public OutputStream open() throws Exception {
4116200253df7b934c807eb3436b51a3b039406457dPaul Hawke            return fstream;
4126200253df7b934c807eb3436b51a3b039406457dPaul Hawke        }
4136200253df7b934c807eb3436b51a3b039406457dPaul Hawke
4146200253df7b934c807eb3436b51a3b039406457dPaul Hawke        @Override
4156200253df7b934c807eb3436b51a3b039406457dPaul Hawke        public void delete() throws Exception {
4166200253df7b934c807eb3436b51a3b039406457dPaul Hawke            file.delete();
4176200253df7b934c807eb3436b51a3b039406457dPaul Hawke        }
4186200253df7b934c807eb3436b51a3b039406457dPaul Hawke
4196200253df7b934c807eb3436b51a3b039406457dPaul Hawke        @Override
4206200253df7b934c807eb3436b51a3b039406457dPaul Hawke        public String getName() {
4216200253df7b934c807eb3436b51a3b039406457dPaul Hawke            return file.getAbsolutePath();
4226200253df7b934c807eb3436b51a3b039406457dPaul Hawke        }
4236200253df7b934c807eb3436b51a3b039406457dPaul Hawke    }
4246200253df7b934c807eb3436b51a3b039406457dPaul Hawke
425d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke    /**
426d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * Handles one session, i.e. parses the HTTP request and returns the response.
427d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     */
428f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke    protected class HTTPSession implements Runnable {
429f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke        public static final int BUFSIZE = 8192;
4304e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        private final TempFileManager tempFileManager;
4317f0727787957c2f093412c01d165846842ec2425Paul Hawke        private InputStream inputStream;
4326200253df7b934c807eb3436b51a3b039406457dPaul Hawke        private OutputStream outputStream;
4339788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke
4346200253df7b934c807eb3436b51a3b039406457dPaul Hawke        public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) {
4356200253df7b934c807eb3436b51a3b039406457dPaul Hawke            this.tempFileManager = tempFileManager;
4367f0727787957c2f093412c01d165846842ec2425Paul Hawke            this.inputStream = inputStream;
4376200253df7b934c807eb3436b51a3b039406457dPaul Hawke            this.outputStream = outputStream;
438d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        }
439d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
440d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        @Override
441d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        public void run() {
442d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            try {
4437f0727787957c2f093412c01d165846842ec2425Paul Hawke                if (inputStream == null) {
444d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    return;
4456200253df7b934c807eb3436b51a3b039406457dPaul Hawke                }
446d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
447d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Read the first 8192 bytes.
448d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // The full header should fit in here.
449d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Apache's default header limit is 8KB.
450d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Do NOT assume that a single read will get the entire header at once!
4517f0727787957c2f093412c01d165846842ec2425Paul Hawke                byte[] buf = new byte[BUFSIZE];
452d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                int splitbyte = 0;
453d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                int rlen = 0;
454d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                {
4557f0727787957c2f093412c01d165846842ec2425Paul Hawke                    int read = inputStream.read(buf, 0, BUFSIZE);
456d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    while (read > 0) {
457d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                        rlen += read;
458d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                        splitbyte = findHeaderEnd(buf, rlen);
459d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                        if (splitbyte > 0)
460d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                            break;
4617f0727787957c2f093412c01d165846842ec2425Paul Hawke                        read = inputStream.read(buf, rlen, BUFSIZE - rlen);
462d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    }
463d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                }
464d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
465d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Create a BufferedReader for parsing the header.
4667f0727787957c2f093412c01d165846842ec2425Paul Hawke                BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, rlen)));
4677f0727787957c2f093412c01d165846842ec2425Paul Hawke                Map<String, String> pre = new HashMap<String, String>();
468d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                Map<String, String> parms = new HashMap<String, String>();
469d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                Map<String, String> header = new HashMap<String, String>();
470d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                Map<String, String> files = new HashMap<String, String>();
471d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
472d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Decode the header into parms and header java properties
473d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                decodeHeader(hin, pre, parms, header);
4747f0727787957c2f093412c01d165846842ec2425Paul Hawke                Method method = Method.lookup(pre.get("method"));
4750cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                if (method == null) {
4767f0727787957c2f093412c01d165846842ec2425Paul Hawke                    Response.error(outputStream, Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error.");
4777f0727787957c2f093412c01d165846842ec2425Paul Hawke                    throw new InterruptedException();
478d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                }
4797f0727787957c2f093412c01d165846842ec2425Paul Hawke                String uri = pre.get("uri");
4807f0727787957c2f093412c01d165846842ec2425Paul Hawke                long size = extractContentLength(header);
481d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
482d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Write the part of body already read to ByteArrayOutputStream f
483dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                RandomAccessFile f = getTmpBucket();
4840cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                if (splitbyte < rlen) {
485d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    f.write(buf, splitbyte, rlen - splitbyte);
4860cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                }
487d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
488d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // While Firefox sends on the first read all the data fitting
489d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // our buffer, Chrome and Opera send only the headers even if
490d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // there is data for the body. We do some magic here to find
491d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // out whether we have already consumed part of body, if we
492d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // have reached the end of the data to be sent or we should
493d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // expect the first byte of the body at the next read.
4940cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                if (splitbyte < rlen) {
495d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    size -= rlen - splitbyte + 1;
4960cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                } else if (splitbyte == 0 || size == 0x7FFFFFFFFFFFFFFFl) {
497d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    size = 0;
4980cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                }
499d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
500d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Now read all the body and write it to f
501d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                buf = new byte[512];
502d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                while (rlen >= 0 && size > 0) {
5037f0727787957c2f093412c01d165846842ec2425Paul Hawke                    rlen = inputStream.read(buf, 0, 512);
504269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    size -= rlen;
5050cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                    if (rlen > 0) {
506269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        f.write(buf, 0, rlen);
5070cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                    }
508269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
509269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
510269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // Get the raw body as a byte []
511dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                ByteBuffer fbuf = f.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, f.length());
512dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                f.seek(0);
513269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
514269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // Create a BufferedReader for easily reading it as string.
515dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                InputStream bin = new FileInputStream(f.getFD());
516269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                BufferedReader in = new BufferedReader(new InputStreamReader(bin));
517269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
518269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // If the method is POST, there may be parameters
519269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // in data section, too, read it:
520df674ca7f11cee6336b66271c50476f389e0ff4bPaul Hawke                if (Method.POST.equals(method)) {
521269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    String contentType = "";
522269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    String contentTypeHeader = header.get("content-type");
5232b54eda5fee8430268a99d73b061e790362db544Tom Hermann
5242c48103a2fc2ef4ccbd5d441da96dc77c7736109Paul Hawke                    StringTokenizer st = null;
5252c48103a2fc2ef4ccbd5d441da96dc77c7736109Paul Hawke                    if (contentTypeHeader != null) {
5262c48103a2fc2ef4ccbd5d441da96dc77c7736109Paul Hawke                        st = new StringTokenizer(contentTypeHeader, ",; ");
5272c48103a2fc2ef4ccbd5d441da96dc77c7736109Paul Hawke                        if (st.hasMoreTokens()) {
5282c48103a2fc2ef4ccbd5d441da96dc77c7736109Paul Hawke                            contentType = st.nextToken();
5292c48103a2fc2ef4ccbd5d441da96dc77c7736109Paul Hawke                        }
530269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
531269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
5320277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                    if ("multipart/form-data".equalsIgnoreCase(contentType)) {
533269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        // Handle multipart/form-data
5340cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                        if (!st.hasMoreTokens()) {
5357f0727787957c2f093412c01d165846842ec2425Paul Hawke                            Response.error(outputStream, Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html");
5367f0727787957c2f093412c01d165846842ec2425Paul Hawke                            throw new InterruptedException();
5370cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                        }
5382b54eda5fee8430268a99d73b061e790362db544Tom Hermann
5394e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke                        String boundaryStartString = "boundary=";
5402b54eda5fee8430268a99d73b061e790362db544Tom Hermann                        int boundaryContentStart = contentTypeHeader.indexOf(boundaryStartString) + boundaryStartString.length();
5414e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke                        String boundary = contentTypeHeader.substring(boundaryContentStart, contentTypeHeader.length());
5424e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke                        if (boundary.startsWith("\"") && boundary.startsWith("\"")) {
5434e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke                            boundary = boundary.substring(1, boundary.length() - 1);
5444e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke                        }
545269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
546269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        decodeMultipartData(boundary, fbuf, in, parms, files);
547269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    } else {
548269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        // Handle application/x-www-form-urlencoded
549269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        String postLine = "";
550269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        char pbuf[] = new char[512];
551269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        int read = in.read(pbuf);
552269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        while (read >= 0 && !postLine.endsWith("\r\n")) {
553269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            postLine += String.valueOf(pbuf, 0, read);
554269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            read = in.read(pbuf);
555269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        }
556269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        postLine = postLine.trim();
557269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        decodeParms(postLine, parms);
558269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
559269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
560269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
561df674ca7f11cee6336b66271c50476f389e0ff4bPaul Hawke                if (Method.PUT.equals(method))
562dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                    files.put("content", saveTmpFile(fbuf, 0, fbuf.limit()));
563269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
564269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // Ok, now do the serve()
565269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                Response r = serve(uri, method, header, parms, files);
5669788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                if (r == null) {
5677f0727787957c2f093412c01d165846842ec2425Paul Hawke                    Response.error(outputStream, Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response.");
5687f0727787957c2f093412c01d165846842ec2425Paul Hawke                    throw new InterruptedException();
5699788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                } else {
5707f0727787957c2f093412c01d165846842ec2425Paul Hawke                    r.send(outputStream);
5719788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                }
572269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
573269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                in.close();
5747f0727787957c2f093412c01d165846842ec2425Paul Hawke                inputStream.close();
575269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            } catch (IOException ioe) {
576269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                try {
5777f0727787957c2f093412c01d165846842ec2425Paul Hawke                    Response.error(outputStream, Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
5787f0727787957c2f093412c01d165846842ec2425Paul Hawke                    throw new InterruptedException();
5797b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                } catch (Throwable ignored) {
580269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
581269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            } catch (InterruptedException ie) {
582269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // Thrown by sendError, ignore and exit the thread.
5834e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke            } finally {
5844e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke                tempFileManager.clear();
585269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
586269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
587269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
5887f0727787957c2f093412c01d165846842ec2425Paul Hawke        private long extractContentLength(Map<String, String> header) {
5897f0727787957c2f093412c01d165846842ec2425Paul Hawke            long size = 0x7FFFFFFFFFFFFFFFl;
5907f0727787957c2f093412c01d165846842ec2425Paul Hawke            String contentLength = header.get("content-length");
5917f0727787957c2f093412c01d165846842ec2425Paul Hawke            if (contentLength != null) {
5927f0727787957c2f093412c01d165846842ec2425Paul Hawke                try {
5937f0727787957c2f093412c01d165846842ec2425Paul Hawke                    size = Integer.parseInt(contentLength);
5947f0727787957c2f093412c01d165846842ec2425Paul Hawke                } catch (NumberFormatException ex) {
5957f0727787957c2f093412c01d165846842ec2425Paul Hawke                    ex.printStackTrace();
5967f0727787957c2f093412c01d165846842ec2425Paul Hawke                }
5977f0727787957c2f093412c01d165846842ec2425Paul Hawke            }
5987f0727787957c2f093412c01d165846842ec2425Paul Hawke            return size;
5997f0727787957c2f093412c01d165846842ec2425Paul Hawke        }
6007f0727787957c2f093412c01d165846842ec2425Paul Hawke
601269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
6027f0727787957c2f093412c01d165846842ec2425Paul Hawke         * Decodes the sent headers and loads the data into Key/value pairs
603d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
6047f0727787957c2f093412c01d165846842ec2425Paul Hawke        private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, String> parms, Map<String, String> header)
605269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                throws InterruptedException {
606269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            try {
607269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // Read the request line
608269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                String inLine = in.readLine();
6090cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                if (inLine == null) {
610269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    return;
6110cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                }
6120cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke
613269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                StringTokenizer st = new StringTokenizer(inLine);
6140cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                if (!st.hasMoreTokens()) {
6157f0727787957c2f093412c01d165846842ec2425Paul Hawke                    Response.error(outputStream, Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html");
6167f0727787957c2f093412c01d165846842ec2425Paul Hawke                    throw new InterruptedException();
6170cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                }
618269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
6190277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                pre.put("method", st.nextToken());
620269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
6210cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                if (!st.hasMoreTokens()) {
6227f0727787957c2f093412c01d165846842ec2425Paul Hawke                    Response.error(outputStream, Response.Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html");
6237f0727787957c2f093412c01d165846842ec2425Paul Hawke                    throw new InterruptedException();
6240cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke                }
625269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
626269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                String uri = st.nextToken();
627269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
628269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // Decode parameters from the URI
629269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                int qmi = uri.indexOf('?');
630269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (qmi >= 0) {
631269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    decodeParms(uri.substring(qmi + 1), parms);
632269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    uri = decodePercent(uri.substring(0, qmi));
6339788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                } else {
634269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    uri = decodePercent(uri);
6359788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                }
636269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
637269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // If there's another token, it's protocol version,
638269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // followed by HTTP headers. Ignore version but parse headers.
639269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // NOTE: this now forces header names lowercase since they are
640269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // case insensitive and vary by client.
641269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (st.hasMoreTokens()) {
642269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    String line = in.readLine();
643269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    while (line != null && line.trim().length() > 0) {
644269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        int p = line.indexOf(':');
645269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        if (p >= 0)
646269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            header.put(line.substring(0, p).trim().toLowerCase(), line.substring(p + 1).trim());
647269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        line = in.readLine();
648269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
649269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
650269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
651269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                pre.put("uri", uri);
652269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            } catch (IOException ioe) {
6537f0727787957c2f093412c01d165846842ec2425Paul Hawke                Response.error(outputStream, Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
6547f0727787957c2f093412c01d165846842ec2425Paul Hawke                throw new InterruptedException();
655269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
656269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
657269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
658269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
6597f0727787957c2f093412c01d165846842ec2425Paul Hawke         * Decodes the Multipart Body data and put it into Key/Value pairs.
660d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
661dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke        private void decodeMultipartData(String boundary, ByteBuffer fbuf, BufferedReader in, Map<String, String> parms,
6629788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke                                         Map<String, String> files) throws InterruptedException {
663269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            try {
664269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                int[] bpositions = getBoundaryPositions(fbuf, boundary.getBytes());
665269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                int boundarycount = 1;
666269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                String mpline = in.readLine();
667269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                while (mpline != null) {
6687b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    if (!mpline.contains(boundary)) {
6697f0727787957c2f093412c01d165846842ec2425Paul Hawke                        Response.error(outputStream, Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html");
6707f0727787957c2f093412c01d165846842ec2425Paul Hawke                        throw new InterruptedException();
6717b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    }
672269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    boundarycount++;
673269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    Map<String, String> item = new HashMap<String, String>();
674269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    mpline = in.readLine();
675269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    while (mpline != null && mpline.trim().length() > 0) {
676269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        int p = mpline.indexOf(':');
677269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        if (p != -1) {
678269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            item.put(mpline.substring(0, p).trim().toLowerCase(), mpline.substring(p + 1).trim());
679269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        }
680269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        mpline = in.readLine();
681269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
682269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    if (mpline != null) {
683269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        String contentDisposition = item.get("content-disposition");
684269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        if (contentDisposition == null) {
6857f0727787957c2f093412c01d165846842ec2425Paul Hawke                            Response.error(outputStream, Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html");
6867f0727787957c2f093412c01d165846842ec2425Paul Hawke                            throw new InterruptedException();
687269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        }
688269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        StringTokenizer st = new StringTokenizer(contentDisposition, "; ");
689269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        Map<String, String> disposition = new HashMap<String, String>();
690269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        while (st.hasMoreTokens()) {
691269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            String token = st.nextToken();
692269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            int p = token.indexOf('=');
693269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            if (p != -1) {
694269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                disposition.put(token.substring(0, p).trim().toLowerCase(), token.substring(p + 1).trim());
695269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            }
696269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        }
697269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        String pname = disposition.get("name");
698269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        pname = pname.substring(1, pname.length() - 1);
699269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
700269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        String value = "";
701269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        if (item.get("content-type") == null) {
7027b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            while (mpline != null && !mpline.contains(boundary)) {
703269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                mpline = in.readLine();
704269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                if (mpline != null) {
705269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                    int d = mpline.indexOf(boundary);
706269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                    if (d == -1) {
707269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                        value += mpline;
708269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                    } else {
709269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                        value += mpline.substring(0, d - 2);
710269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                    }
711269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                }
712269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            }
713269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        } else {
714269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            if (boundarycount > bpositions.length) {
7157f0727787957c2f093412c01d165846842ec2425Paul Hawke                                Response.error(outputStream, Response.Status.INTERNAL_ERROR, "Error processing request");
7167f0727787957c2f093412c01d165846842ec2425Paul Hawke                                throw new InterruptedException();
717269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            }
718269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            int offset = stripMultipartHeaders(fbuf, bpositions[boundarycount - 2]);
719269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            String path = saveTmpFile(fbuf, offset, bpositions[boundarycount - 1] - offset - 4);
720269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            files.put(pname, path);
721269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            value = disposition.get("filename");
722269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            value = value.substring(1, value.length() - 1);
723269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            do {
724269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                mpline = in.readLine();
7257b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            } while (mpline != null && !mpline.contains(boundary));
726269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        }
727269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        parms.put(pname, value);
728269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
729269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
730269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            } catch (IOException ioe) {
7317f0727787957c2f093412c01d165846842ec2425Paul Hawke                Response.error(outputStream, Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
7327f0727787957c2f093412c01d165846842ec2425Paul Hawke                throw new InterruptedException();
733269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
734269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
735269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
736269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
737269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * Find byte index separating header from body. It must be the last byte of the first two sequential new lines.
738d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
739269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        private int findHeaderEnd(final byte[] buf, int rlen) {
740269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            int splitbyte = 0;
741269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            while (splitbyte + 3 < rlen) {
742269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') {
743269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    return splitbyte + 4;
744269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
745269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                splitbyte++;
746269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
747269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            return 0;
748269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
749269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
750269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
751269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * Find the byte positions where multipart boundaries start.
752d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
753dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke        public int[] getBoundaryPositions(ByteBuffer b, byte[] boundary) {
754269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            int matchcount = 0;
755269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            int matchbyte = -1;
756269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            List<Integer> matchbytes = new ArrayList<Integer>();
757dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke            for (int i=0; i<b.limit(); i++) {
758dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                if (b.get(i) == boundary[matchcount]) {
759269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    if (matchcount == 0)
760269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        matchbyte = i;
761269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    matchcount++;
762269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    if (matchcount == boundary.length) {
763269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        matchbytes.add(matchbyte);
764269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        matchcount = 0;
765269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        matchbyte = -1;
766269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
767269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                } else {
768269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    i -= matchcount;
769269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    matchcount = 0;
770269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    matchbyte = -1;
771269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
772269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
773269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            int[] ret = new int[matchbytes.size()];
774269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            for (int i = 0; i < ret.length; i++) {
7757b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                ret[i] = matchbytes.get(i);
776269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
777269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            return ret;
778269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
779269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
780269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
781269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * Retrieves the content of a sent file and saves it to a temporary file. The full path to the saved file is returned.
782d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
783dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke        private String saveTmpFile(ByteBuffer  b, int offset, int len) {
784269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            String path = "";
785269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            if (len > 0) {
786269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                try {
7874e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke                    TempFile tempFile = tempFileManager.createTempFile();
788dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                    ByteBuffer src = b.duplicate();
789dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                    FileChannel dest = new FileOutputStream(tempFile.getName()).getChannel();
790dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                    src.position(offset).limit(offset + len);
791dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                    dest.write(src.slice());
7924e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke                    path = tempFile.getName();
793269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                } catch (Exception e) { // Catch exception if any
794d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    System.err.println("Error: " + e.getMessage());
795269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
796269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
797269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            return path;
798269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
799269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
800dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke        private RandomAccessFile getTmpBucket() throws IOException {
801dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke            try {
802dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                TempFile tempFile = tempFileManager.createTempFile();
803dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                return new RandomAccessFile(tempFile.getName(), "rw");
804dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke            } catch (Exception e) {
805dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                System.err.println("Error: " + e.getMessage());
806dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke            }
807dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke            return null;
808dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke        }
809dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke
810269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
811269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * It returns the offset separating multipart file headers from the file's data.
812269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         */
813dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke        private int stripMultipartHeaders(ByteBuffer b, int offset) {
814269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            int i;
815dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke            for (i=offset; i<b.limit(); i++) {
816dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke                if (b.get(i) == '\r' && b.get(++i) == '\n' && b.get(++i) == '\r' && b.get(++i) == '\n') {
817269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    break;
818269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
819269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
820269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            return i + 1;
821269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
822269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
823269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
8247f0727787957c2f093412c01d165846842ec2425Paul Hawke         * Decodes parameters in percent-encoded URI-format ( e.g. "name=Jack%20Daniels&pass=Single%20Malt" ) and
8257f0727787957c2f093412c01d165846842ec2425Paul Hawke         * adds them to given Map. NOTE: this doesn't support multiple identical keys due to the simplicity of Map.
826269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         */
827269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        private void decodeParms(String parms, Map<String, String> p) throws InterruptedException {
828f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            if (parms == null) {
829f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                p.put(QUERY_STRING_PARAMETER, "");
830269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                return;
831f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            }
832269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
833f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            p.put(QUERY_STRING_PARAMETER, parms);
834269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            StringTokenizer st = new StringTokenizer(parms, "&");
835f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            try {
836f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                while (st.hasMoreTokens()) {
837f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    String e = st.nextToken();
838f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    int sep = e.indexOf('=');
839f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    if (sep >= 0) {
840f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                        p.put(decodePercent(e.substring(0, sep)).trim(),
841f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                                decodePercent(e.substring(sep + 1)));
842f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    } else {
843f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                        p.put(decodePercent(e).trim(), "");
844f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                    }
845269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
846f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke            } catch (InterruptedException e) {
847f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke                Response.error(outputStream, Response.Status.BAD_REQUEST, "BAD REQUEST: Bad percent-encoding.");
848269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
849269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
850269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    }
8514e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke
8524e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke    private class DefaultTempFileManagerFactory implements TempFileManagerFactory {
8534e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        @Override
8544e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        public TempFileManager create() {
8554e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke            return new DefaultTempFileManager();
8564e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        }
8574e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke    }
8584e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke
859dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke    public static class DefaultTempFileManager implements TempFileManager {
8604e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        private final String tmpdir;
8614e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        private final List<TempFile> tempFiles;
8624e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke
863dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke        public DefaultTempFileManager() {
8644e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke            tmpdir = System.getProperty("java.io.tmpdir");
8654e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke            tempFiles = new ArrayList<TempFile>();
8664e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        }
8674e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke
8684e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        @Override
8694e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        public TempFile createTempFile() throws Exception {
8704e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke            DefaultTempFile tempFile = new DefaultTempFile(tmpdir);
8714e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke            tempFiles.add(tempFile);
8724e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke            return tempFile;
8734e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        }
8744e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke
8754e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        @Override
8764e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        public void clear() {
8774e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke            for (TempFile file : tempFiles) {
8784e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke                try {
8794e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke                    file.delete();
8806200253df7b934c807eb3436b51a3b039406457dPaul Hawke                } catch (Exception ignored) {
8816200253df7b934c807eb3436b51a3b039406457dPaul Hawke                }
8824e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke            }
8834e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke            tempFiles.clear();
8844e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        }
8854e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke    }
8864e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke
8874e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke    private class DefaultAsyncRunner implements AsyncRunner {
8884e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        @Override
8894e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        public void exec(Runnable code) {
8904e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke            Thread t = new Thread(code);
8914e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke            t.setDaemon(true);
8924e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke            t.start();
8934e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke        }
8944e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke    }
895269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke}
896