NanoHTTPD.java revision 0277bbd03983f15110fcf0d039a3e276d0808e29
1269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkepackage fi.iki.elonen;
2269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
3d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawkeimport java.io.*;
4269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.net.ServerSocket;
5269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.net.Socket;
6269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.net.URLEncoder;
77b88819e82b89ac3476ce060903468076a73de8fPaul Hawkeimport java.text.SimpleDateFormat;
8269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.util.ArrayList;
9269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.util.Date;
10269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.util.HashMap;
11269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.util.Iterator;
12269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.util.List;
13269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.util.Locale;
14269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.util.Map;
15269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.util.Properties;
16269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.util.StringTokenizer;
17269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkeimport java.util.TimeZone;
18269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
19269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke/**
20269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * A simple, tiny, nicely embeddable HTTP 1.0 (partially 1.1) server in Java
21d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
22d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
23269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * NanoHTTPD version 1.25, Copyright &copy; 2001,2005-2012 Jarno Elonen (elonen@iki.fi, http://iki.fi/elonen/) and Copyright &copy; 2010
24269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * Konstantinos Togias (info@ktogias.gr, http://ktogias.gr)
25d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
26d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
27269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <b>Features + limitations: </b>
28269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <ul>
29d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
30269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Only one Java file</li>
31269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Java 1.1 compatible</li>
32269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Released as open source, Modified BSD licence</li>
33269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>No fixed config files, logging, authorization etc. (Implement yourself if you need them.)</li>
34269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports parameter parsing of GET and POST methods (+ rudimentary PUT support in 1.25)</li>
35269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports both dynamic content and file serving</li>
36269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports file upload (since version 1.2, 2010)</li>
37269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports partial content (streaming)</li>
38269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports ETags</li>
39269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Never caches anything</li>
40269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Doesn't limit bandwidth, request time or simultaneous connections</li>
41269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Default code serves files and shows all HTTP parameters and headers</li>
42269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server supports directory listing, index.html and index.htm</li>
43269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server supports partial content (streaming)</li>
44269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server supports ETags</li>
45269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server does the 301 redirection trick for directories without '/'</li>
46269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server supports simple skipping for files (continue download)</li>
47269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server serves also very long files without memory overhead</li>
48269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Contains a built-in list of most common mime types</li>
49269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>All header names are converted lowercase so they don't vary between browsers/clients</li>
50d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
51269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * </ul>
52d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
53d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
54269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <b>Ways to use: </b>
55269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <ul>
56d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
57269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Run as a standalone app, serves files and shows requests</li>
58269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Subclass serve() and embed to your own program</li>
59269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Call serveFile() from serve() with your own base directory</li>
60d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
61269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * </ul>
62d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/>
63269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * See the end of the source file for distribution license (Modified BSD licence)
64269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke */
65d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawkepublic abstract class NanoHTTPD {
66269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    /**
67d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE
68269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     */
69d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke    private static final Map<String, String> MIME_TYPES;
70d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke    static {
71d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        Map<String, String> mime = new HashMap<String, String>();
72d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("css", "text/css");
73d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("htm", "text/html");
74d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("html", "text/html");
75d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("xml", "text/xml");
76d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("txt", "text/plain");
77d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("asc", "text/plain");
78d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("gif", "image/gif");
79d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("jpg", "image/jpeg");
80d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("jpeg", "image/jpeg");
81d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("png", "image/png");
82d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("mp3", "audio/mpeg");
83d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("m3u", "audio/mpeg-url");
84d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("mp4", "video/mp4");
85d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("ogv", "video/ogg");
86d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("flv", "video/x-flv");
87d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("mov", "video/quicktime");
88d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("swf", "application/x-shockwave-flash");
89d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("js", "application/javascript");
90d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("pdf", "application/pdf");
91d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("doc", "application/msword");
92d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("ogg", "application/x-ogg");
93d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("zip", "application/octet-stream");
94d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("exe", "application/octet-stream");
95d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        mime.put("class", "application/octet-stream");
96d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        MIME_TYPES = mime;
97269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    }
98269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
990277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke    public enum METHOD {
1000277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke        GET, PUT, POST, DELETE;
1010277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke
1020277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke        public boolean equalsIgnoreCase(String method) {
1030277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            return toString().equalsIgnoreCase(method);
1040277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke        }
1050277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke
1060277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke        static METHOD lookup(String method) {
1070277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            for (METHOD m : METHOD.values()) {
1080277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                if (m.toString().equalsIgnoreCase(method)) {
1090277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                    return m;
1100277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                }
1110277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            }
1120277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            return null;
1130277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke        }
1140277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke    }
115269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
116269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    /**
117269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     * Common mime types for dynamic content
118269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     */
119269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    public static final String MIME_PLAINTEXT = "text/plain";
120269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    public static final String MIME_HTML = "text/html";
121269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    public static final String MIME_DEFAULT_BINARY = "application/octet-stream";
122269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
123d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke    private final File myRootDir;
1247b88819e82b89ac3476ce060903468076a73de8fPaul Hawke    private final int myPort;
1257b88819e82b89ac3476ce060903468076a73de8fPaul Hawke    private ServerSocket myServerSocket;
1267b88819e82b89ac3476ce060903468076a73de8fPaul Hawke    private Thread myThread;
127269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
128269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    /**
129d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * Constructs an HTTP server on given port.
130269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     */
1317b88819e82b89ac3476ce060903468076a73de8fPaul Hawke    public NanoHTTPD(int port, File wwwroot) {
1327b88819e82b89ac3476ce060903468076a73de8fPaul Hawke        this.myPort = port;
133269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        this.myRootDir = wwwroot;
1343f2aa2e2e0e02fe7e0151eb64d31265b578885ecPaul Hawke    }
1353f2aa2e2e0e02fe7e0151eb64d31265b578885ecPaul Hawke
1363f2aa2e2e0e02fe7e0151eb64d31265b578885ecPaul Hawke    /**
1373f2aa2e2e0e02fe7e0151eb64d31265b578885ecPaul Hawke     * Starts the server
138d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * <p/>
139d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * Throws an IOException if the socket is already in use
1403f2aa2e2e0e02fe7e0151eb64d31265b578885ecPaul Hawke     */
1417b88819e82b89ac3476ce060903468076a73de8fPaul Hawke    public void start() throws IOException {
1427b88819e82b89ac3476ce060903468076a73de8fPaul Hawke        this.myServerSocket = new ServerSocket(myPort);
143269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        myThread = new Thread(new Runnable() {
144269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            @Override
145269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            public void run() {
146269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                try {
1477b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    do {
148269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        new HTTPSession(myServerSocket.accept());
1497b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    } while (true);
1507b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                } catch (IOException ignored) {
151269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
152269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
153269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        });
154269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        myThread.setDaemon(true);
155269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        myThread.start();
156269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    }
157d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
158269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    /**
159269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     * Stops the server.
160269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     */
161269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    public void stop() {
162269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        try {
163269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            myServerSocket.close();
164269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            myThread.join();
165269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        } catch (IOException ioe) {
1667b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            ioe.printStackTrace();
167269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        } catch (InterruptedException e) {
1687b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            e.printStackTrace();
169269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
170269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    }
171269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
172a6619fe65dd3a997960724faf7c51685de793f91Paul Hawke    /**
173a6619fe65dd3a997960724faf7c51685de793f91Paul Hawke     * Return the HTTP root directory for this server
174a6619fe65dd3a997960724faf7c51685de793f91Paul Hawke     */
175d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke    public File getRootDir() {
176d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        return myRootDir;
177d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke    }
178d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
179269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    /**
180d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * Override this to customize the server.
181d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * <p/>
182d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * <p/>
183d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * (By default, this delegates to serveFile() and allows directory listing.)
184d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     *
185d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * @param uri    Percent-decoded URI without parameters, for example "/index.cgi"
186d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * @param method "GET", "POST" etc.
187d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * @param parms  Parsed, percent decoded parameters from URI and, in case of POST, data.
188d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * @param header Header entries, percent decoded
189d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * @return HTTP response, see class Response for details
190269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     */
1910277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke    public abstract Response serve(String uri, METHOD method, Map<String, String> header, Map<String, String> parms, Map<String, String> files);
192269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
193d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke    /**
194d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * Handles one session, i.e. parses the HTTP request and returns the response.
195d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     */
196d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke    private class HTTPSession implements Runnable {
197d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        public HTTPSession(Socket s) {
198d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            mySocket = s;
199d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            Thread t = new Thread(this);
200d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            t.setDaemon(true);
201d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            t.start();
202d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        }
203d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
204d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        @Override
205d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        public void run() {
206d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            try {
207d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                InputStream is = mySocket.getInputStream();
208d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                if (is == null)
209d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    return;
210d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
211d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Read the first 8192 bytes.
212d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // The full header should fit in here.
213d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Apache's default header limit is 8KB.
214d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Do NOT assume that a single read will get the entire header at once!
215d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                final int bufsize = 8192;
216d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                byte[] buf = new byte[bufsize];
217d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                int splitbyte = 0;
218d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                int rlen = 0;
219d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                {
220d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    int read = is.read(buf, 0, bufsize);
221d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    while (read > 0) {
222d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                        rlen += read;
223d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                        splitbyte = findHeaderEnd(buf, rlen);
224d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                        if (splitbyte > 0)
225d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                            break;
226d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                        read = is.read(buf, rlen, bufsize - rlen);
227d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    }
228d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                }
229d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
230d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Create a BufferedReader for parsing the header.
231d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                ByteArrayInputStream hbis = new ByteArrayInputStream(buf, 0, rlen);
232d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                BufferedReader hin = new BufferedReader(new InputStreamReader(hbis));
233d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                Properties pre = new Properties();
234d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                Map<String, String> parms = new HashMap<String, String>();
235d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                Map<String, String> header = new HashMap<String, String>();
236d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                Map<String, String> files = new HashMap<String, String>();
237d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
238d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Decode the header into parms and header java properties
239d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                decodeHeader(hin, pre, parms, header);
2400277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                String methodStr = pre.getProperty("method");
2410277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                METHOD method = METHOD.lookup(methodStr);
242d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                String uri = pre.getProperty("uri");
243d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
244d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                long size = 0x7FFFFFFFFFFFFFFFl;
245d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                String contentLength = header.get("content-length");
246d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                if (contentLength != null) {
247d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    try {
248d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                        size = Integer.parseInt(contentLength);
249d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    } catch (NumberFormatException ex) {
2507b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        ex.printStackTrace();
251d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    }
252d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                }
253d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
254d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Write the part of body already read to ByteArrayOutputStream f
255d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                ByteArrayOutputStream f = new ByteArrayOutputStream();
256d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                if (splitbyte < rlen)
257d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    f.write(buf, splitbyte, rlen - splitbyte);
258d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
259d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // While Firefox sends on the first read all the data fitting
260d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // our buffer, Chrome and Opera send only the headers even if
261d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // there is data for the body. We do some magic here to find
262d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // out whether we have already consumed part of body, if we
263d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // have reached the end of the data to be sent or we should
264d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // expect the first byte of the body at the next read.
265d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                if (splitbyte < rlen)
266d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    size -= rlen - splitbyte + 1;
267d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                else if (splitbyte == 0 || size == 0x7FFFFFFFFFFFFFFFl)
268d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    size = 0;
269d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
270d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                // Now read all the body and write it to f
271d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                buf = new byte[512];
272d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                while (rlen >= 0 && size > 0) {
273269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    rlen = is.read(buf, 0, 512);
274269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    size -= rlen;
275269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    if (rlen > 0)
276269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        f.write(buf, 0, rlen);
277269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
278269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
279269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // Get the raw body as a byte []
280269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                byte[] fbuf = f.toByteArray();
281269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
282269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // Create a BufferedReader for easily reading it as string.
283269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                ByteArrayInputStream bin = new ByteArrayInputStream(fbuf);
284269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                BufferedReader in = new BufferedReader(new InputStreamReader(bin));
285269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
286269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // If the method is POST, there may be parameters
287269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // in data section, too, read it:
2880277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                if (METHOD.POST.equals(method)) {
289269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    String contentType = "";
290269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    String contentTypeHeader = header.get("content-type");
291269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    StringTokenizer st = new StringTokenizer(contentTypeHeader, "; ");
292269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    if (st.hasMoreTokens()) {
293269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        contentType = st.nextToken();
294269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
295269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
2960277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                    if ("multipart/form-data".equalsIgnoreCase(contentType)) {
297269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        // Handle multipart/form-data
298269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        if (!st.hasMoreTokens())
2990277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                            sendError(Response.HTTP_STATUS.BAD_REQUEST,
300269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                    "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html");
301269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        String boundaryExp = st.nextToken();
302269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        st = new StringTokenizer(boundaryExp, "=");
303269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        if (st.countTokens() != 2)
3040277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                            sendError(Response.HTTP_STATUS.BAD_REQUEST,
305269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                    "BAD REQUEST: Content type is multipart/form-data but boundary syntax error. Usage: GET /example/file.html");
306269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        st.nextToken();
307269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        String boundary = st.nextToken();
308269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
309269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        decodeMultipartData(boundary, fbuf, in, parms, files);
310269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    } else {
311269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        // Handle application/x-www-form-urlencoded
312269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        String postLine = "";
313269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        char pbuf[] = new char[512];
314269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        int read = in.read(pbuf);
315269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        while (read >= 0 && !postLine.endsWith("\r\n")) {
316269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            postLine += String.valueOf(pbuf, 0, read);
317269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            read = in.read(pbuf);
318269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        }
319269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        postLine = postLine.trim();
320269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        decodeParms(postLine, parms);
321269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
322269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
323269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
3240277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                if (METHOD.PUT.equals(method))
325269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    files.put("content", saveTmpFile(fbuf, 0, f.size()));
326269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
327269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // Ok, now do the serve()
328269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                Response r = serve(uri, method, header, parms, files);
329269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (r == null)
3300277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                    sendError(Response.HTTP_STATUS.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response.");
331269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                else
332269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    sendResponse(r.status, r.mimeType, r.header, r.data);
333269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
334269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                in.close();
335269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                is.close();
336269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            } catch (IOException ioe) {
337269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                try {
3380277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                    sendError(Response.HTTP_STATUS.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
3397b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                } catch (Throwable ignored) {
340269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
341269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            } catch (InterruptedException ie) {
342269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // Thrown by sendError, ignore and exit the thread.
343269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
344269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
345269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
346269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
347269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * Decodes the sent headers and loads the data into java Properties' key - value pairs
348d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
349269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        private void decodeHeader(BufferedReader in, Properties pre, Map<String, String> parms, Map<String, String> header)
350269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                throws InterruptedException {
351269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            try {
352269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // Read the request line
353269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                String inLine = in.readLine();
354269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (inLine == null)
355269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    return;
356269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                StringTokenizer st = new StringTokenizer(inLine);
357269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (!st.hasMoreTokens())
3580277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                    sendError(Response.HTTP_STATUS.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html");
359269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
3600277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                pre.put("method", st.nextToken());
361269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
362269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (!st.hasMoreTokens())
3630277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                    sendError(Response.HTTP_STATUS.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html");
364269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
365269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                String uri = st.nextToken();
366269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
367269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // Decode parameters from the URI
368269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                int qmi = uri.indexOf('?');
369269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (qmi >= 0) {
370269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    decodeParms(uri.substring(qmi + 1), parms);
371269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    uri = decodePercent(uri.substring(0, qmi));
372269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                } else
373269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    uri = decodePercent(uri);
374269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
375269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // If there's another token, it's protocol version,
376269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // followed by HTTP headers. Ignore version but parse headers.
377269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // NOTE: this now forces header names lowercase since they are
378269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // case insensitive and vary by client.
379269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (st.hasMoreTokens()) {
380269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    String line = in.readLine();
381269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    while (line != null && line.trim().length() > 0) {
382269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        int p = line.indexOf(':');
383269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        if (p >= 0)
384269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            header.put(line.substring(0, p).trim().toLowerCase(), line.substring(p + 1).trim());
385269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        line = in.readLine();
386269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
387269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
388269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
389269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                pre.put("uri", uri);
390269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            } catch (IOException ioe) {
3910277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                sendError(Response.HTTP_STATUS.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
392269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
393269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
394269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
395269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
396269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * Decodes the Multipart Body data and put it into java Properties' key - value pairs.
397d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
398269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        private void decodeMultipartData(String boundary, byte[] fbuf, BufferedReader in, Map<String, String> parms,
399d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                                         Map<String, String> files) throws InterruptedException {
400269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            try {
401269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                int[] bpositions = getBoundaryPositions(fbuf, boundary.getBytes());
402269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                int boundarycount = 1;
403269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                String mpline = in.readLine();
404269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                while (mpline != null) {
4057b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    if (!mpline.contains(boundary)) {
4060277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                        sendError(Response.HTTP_STATUS.BAD_REQUEST,
407269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html");
4087b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    }
409269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    boundarycount++;
410269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    Map<String, String> item = new HashMap<String, String>();
411269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    mpline = in.readLine();
412269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    while (mpline != null && mpline.trim().length() > 0) {
413269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        int p = mpline.indexOf(':');
414269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        if (p != -1) {
415269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            item.put(mpline.substring(0, p).trim().toLowerCase(), mpline.substring(p + 1).trim());
416269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        }
417269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        mpline = in.readLine();
418269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
419269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    if (mpline != null) {
420269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        String contentDisposition = item.get("content-disposition");
421269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        if (contentDisposition == null) {
4220277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                            sendError(Response.HTTP_STATUS.BAD_REQUEST,
423269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                    "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html");
424269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        }
425269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        StringTokenizer st = new StringTokenizer(contentDisposition, "; ");
426269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        Map<String, String> disposition = new HashMap<String, String>();
427269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        while (st.hasMoreTokens()) {
428269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            String token = st.nextToken();
429269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            int p = token.indexOf('=');
430269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            if (p != -1) {
431269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                disposition.put(token.substring(0, p).trim().toLowerCase(), token.substring(p + 1).trim());
432269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            }
433269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        }
434269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        String pname = disposition.get("name");
435269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        pname = pname.substring(1, pname.length() - 1);
436269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
437269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        String value = "";
438269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        if (item.get("content-type") == null) {
4397b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            while (mpline != null && !mpline.contains(boundary)) {
440269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                mpline = in.readLine();
441269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                if (mpline != null) {
442269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                    int d = mpline.indexOf(boundary);
443269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                    if (d == -1) {
444269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                        value += mpline;
445269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                    } else {
446269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                        value += mpline.substring(0, d - 2);
447269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                    }
448269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                }
449269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            }
450269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        } else {
451269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            if (boundarycount > bpositions.length) {
4520277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                                sendError(Response.HTTP_STATUS.INTERNAL_ERROR, "Error processing request");
453269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            }
454269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            int offset = stripMultipartHeaders(fbuf, bpositions[boundarycount - 2]);
455269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            String path = saveTmpFile(fbuf, offset, bpositions[boundarycount - 1] - offset - 4);
456269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            files.put(pname, path);
457269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            value = disposition.get("filename");
458269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            value = value.substring(1, value.length() - 1);
459269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            do {
460269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                                mpline = in.readLine();
4617b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            } while (mpline != null && !mpline.contains(boundary));
462269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        }
463269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        parms.put(pname, value);
464269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
465269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
466269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            } catch (IOException ioe) {
4670277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                sendError(Response.HTTP_STATUS.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
468269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
469269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
470269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
471269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
472269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * Find byte index separating header from body. It must be the last byte of the first two sequential new lines.
473d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
474269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        private int findHeaderEnd(final byte[] buf, int rlen) {
475269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            int splitbyte = 0;
476269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            while (splitbyte + 3 < rlen) {
477269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') {
478269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    return splitbyte + 4;
479269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
480269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                splitbyte++;
481269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
482269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            return 0;
483269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
484269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
485269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
486269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * Find the byte positions where multipart boundaries start.
487d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
488269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        public int[] getBoundaryPositions(byte[] b, byte[] boundary) {
489269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            int matchcount = 0;
490269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            int matchbyte = -1;
491269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            List<Integer> matchbytes = new ArrayList<Integer>();
492269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            for (int i = 0; i < b.length; i++) {
493269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (b[i] == boundary[matchcount]) {
494269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    if (matchcount == 0)
495269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        matchbyte = i;
496269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    matchcount++;
497269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    if (matchcount == boundary.length) {
498269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        matchbytes.add(matchbyte);
499269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        matchcount = 0;
500269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        matchbyte = -1;
501269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
502269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                } else {
503269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    i -= matchcount;
504269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    matchcount = 0;
505269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    matchbyte = -1;
506269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
507269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
508269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            int[] ret = new int[matchbytes.size()];
509269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            for (int i = 0; i < ret.length; i++) {
5107b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                ret[i] = matchbytes.get(i);
511269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
512269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            return ret;
513269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
514269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
515269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
516269baff79590de5abd70e8e05bf46547e4a28ee6Micah 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.
517d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
518269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        private String saveTmpFile(byte[] b, int offset, int len) {
519269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            String path = "";
520269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            if (len > 0) {
521269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                String tmpdir = System.getProperty("java.io.tmpdir");
522269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                try {
523269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    File temp = File.createTempFile("NanoHTTPD", "", new File(tmpdir));
524269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    OutputStream fstream = new FileOutputStream(temp);
525269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    fstream.write(b, offset, len);
526269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    fstream.close();
527269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    path = temp.getAbsolutePath();
528269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                } catch (Exception e) { // Catch exception if any
529d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    System.err.println("Error: " + e.getMessage());
530269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
531269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
532269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            return path;
533269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
534269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
535269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
536269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * It returns the offset separating multipart file headers from the file's data.
537269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         */
538269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        private int stripMultipartHeaders(byte[] b, int offset) {
539269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            int i;
540269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            for (i = offset; i < b.length; i++) {
541269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (b[i] == '\r' && b[++i] == '\n' && b[++i] == '\r' && b[++i] == '\n') {
542269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    break;
543269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
544269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
545269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            return i + 1;
546269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
547269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
548269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
549269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * Decodes the percent encoding scheme. <br/>
550269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * For example: "an+example%20string" -> "an example string"
551269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         */
552269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        private String decodePercent(String str) throws InterruptedException {
553269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            try {
5547b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                StringBuilder sb = new StringBuilder();
555269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                for (int i = 0; i < str.length(); i++) {
556269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    char c = str.charAt(i);
557269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    switch (c) {
558d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                        case '+':
559d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                            sb.append(' ');
560d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                            break;
561d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                        case '%':
562d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                            sb.append((char) Integer.parseInt(str.substring(i + 1, i + 3), 16));
563d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                            i += 2;
564d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                            break;
565d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                        default:
566d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                            sb.append(c);
567d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                            break;
568269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
569269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
570269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                return sb.toString();
571269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            } catch (Exception e) {
5720277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                sendError(Response.HTTP_STATUS.BAD_REQUEST, "BAD REQUEST: Bad percent-encoding.");
573269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                return null;
574269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
575269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
576269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
577269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
578269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * Decodes parameters in percent-encoded URI-format ( e.g. "name=Jack%20Daniels&pass=Single%20Malt" ) and adds them to given
579269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * Properties. NOTE: this doesn't support multiple identical keys due to the simplicity of Properties -- if you need multiples, you
580269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * might want to replace the Properties with a Hashtable of Vectors or such.
581269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         */
582269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        private void decodeParms(String parms, Map<String, String> p) throws InterruptedException {
583269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            if (parms == null)
584269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                return;
585269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
586269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            StringTokenizer st = new StringTokenizer(parms, "&");
587269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            while (st.hasMoreTokens()) {
588269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                String e = st.nextToken();
589269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                int sep = e.indexOf('=');
590269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (sep >= 0) {
591269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    p.put(decodePercent(e.substring(0, sep)).trim(), decodePercent(e.substring(sep + 1)));
592269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
593269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
594269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
595269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
596269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
597269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * Returns an error message as a HTTP response and throws InterruptedException to stop further request processing.
598269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         */
5990277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke        private void sendError(Response.HTTP_STATUS status, String msg) throws InterruptedException {
600269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            sendResponse(status, MIME_PLAINTEXT, null, new ByteArrayInputStream(msg.getBytes()));
601269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            throw new InterruptedException();
602269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
603269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
604269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        /**
605269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         * Sends given response to the socket.
606269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke         */
6070277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke        private void sendResponse(Response.HTTP_STATUS status, String mime, Map<String, String> header, InputStream data) {
6087b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
6097b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));
6107b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
611269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            try {
612269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (status == null) {
613269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    throw new Error("sendResponse(): Status can't be null.");
614269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
615269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                OutputStream out = mySocket.getOutputStream();
616269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                PrintWriter pw = new PrintWriter(out);
617269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                pw.print("HTTP/1.0 " + status + " \r\n");
618269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
619269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (mime != null) {
620269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    pw.print("Content-Type: " + mime + "\r\n");
621269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
622269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
623269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (header == null || header.get("Date") == null) {
624269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n");
625269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
626269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
627269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (header != null) {
6287b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    for (String key : header.keySet()) {
629269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        String value = header.get(key);
630269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        pw.print(key + ": " + value + "\r\n");
631269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
632269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
633269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
634269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                pw.print("\r\n");
635269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                pw.flush();
636269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
637269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (data != null) {
638269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    int pending = data.available(); // This is to support partial sends, see serveFile()
639d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    int BUFFER_SIZE = 16 * 1024;
640269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    byte[] buff = new byte[BUFFER_SIZE];
641269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    while (pending > 0) {
642269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        int read = data.read(buff, 0, ((pending > BUFFER_SIZE) ? BUFFER_SIZE : pending));
643269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        if (read <= 0) {
644269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                            break;
645269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        }
646269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        out.write(buff, 0, read);
647269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                        pending -= read;
648269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    }
649269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
650269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                out.flush();
651269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                out.close();
652269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                if (data != null)
653269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    data.close();
654269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            } catch (IOException ioe) {
655269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                // Couldn't write? No can do.
656269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                try {
657269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                    mySocket.close();
6587b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                } catch (Throwable ignored) {
659269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke                }
660269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
661269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
662269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
663269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        private final Socket mySocket;
664269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    }
665269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
666269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    /**
667d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * HTTP response. Return one of these from serve().
668269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     */
6690277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke    public static class Response {
6700277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke        /**
6710277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke         * Some HTTP response status codes
6720277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke         */
6730277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke        public enum HTTP_STATUS {
6740277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            OK(200, "OK"),
6750277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            PARTIAL_CONTENT(206, "Partial Content"),
6760277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"),
6770277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            REDIRECT(301, "Moved Permanently"),
6780277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            NOT_MODIFIED(304, "Not Modified"),
6790277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            FORBIDDEN(403, "Forbidden"),
6800277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            NOT_FOUND(404, "Not Found"),
6810277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            BAD_REQUEST(400, "Bad Request"),
6820277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            INTERNAL_ERROR(500, "Internal Server Error");
6830277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke
6840277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            HTTP_STATUS(int requestStatus, String descr) {
6850277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                this.requestStatus = requestStatus;
6860277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                this.descr = descr;
6870277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            }
6880277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            private int requestStatus;
6890277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            private String descr;
6900277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke
6910277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            public int getRequestStatus() { return this.requestStatus; }
6920277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            public String getDescription() { return ""+this.requestStatus+" "+descr; }
6930277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke        }
6940277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke
695d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        /**
696d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         * Default constructor: response = HTTP_OK, mime = MIME_HTML and your supplied message
697d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
698d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        public Response(String msg) {
6990277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            this(Response.HTTP_STATUS.OK, MIME_HTML, msg);
700269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
701269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
702d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        /**
703d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         * Basic constructor.
704d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
7050277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke        public Response(HTTP_STATUS status, String mimeType, InputStream data) {
706d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            this.status = status;
707d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            this.mimeType = mimeType;
708d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            this.data = data;
709269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
710269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
711d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        /**
712d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         * Convenience method that makes an InputStream out of given text.
713d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
7140277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke        public Response(HTTP_STATUS status, String mimeType, String txt) {
715d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            this.status = status;
716d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            this.mimeType = mimeType;
717d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            try {
718d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                this.data = new ByteArrayInputStream(txt.getBytes("UTF-8"));
719d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            } catch (java.io.UnsupportedEncodingException uee) {
720d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                uee.printStackTrace();
721269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            }
722269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
723269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
724d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        /**
725d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         * Adds given line to the header.
726d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
727d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        public void addHeader(String name, String value) {
728d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            header.put(name, value);
729269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke        }
730269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
731d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        /**
732d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         * HTTP status code after processing, e.g. "200 OK", HTTP_OK
733d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
7340277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke        public HTTP_STATUS status;
735269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
736d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        /**
737d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         * MIME type of content, e.g. "text/html"
738d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
739d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        public String mimeType;
740269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
741d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        /**
742d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         * Data of the response, may be null.
743d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
744d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        public InputStream data;
745269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
746d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        /**
747d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         * Headers for the HTTP response. Use addHeader() to add lines.
748d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke         */
749d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        public Map<String, String> header = new HashMap<String, String>();
750269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    }
751269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke
752269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    /**
753269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     * The distribution licence
754269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke     */
755269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke    private static final String LICENCE = "Copyright (C) 2001,2005-2011 by Jarno Elonen <elonen@iki.fi>\n"
756269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "and Copyright (C) 2010 by Konstantinos Togias <info@ktogias.gr>\n" + "\n"
757269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "Redistribution and use in source and binary forms, with or without\n"
758269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "modification, are permitted provided that the following conditions\n" + "are met:\n" + "\n"
759269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "Redistributions of source code must retain the above copyright notice,\n"
760269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "this list of conditions and the following disclaimer. Redistributions in\n"
761269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "binary form must reproduce the above copyright notice, this list of\n"
762269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "conditions and the following disclaimer in the documentation and/or other\n"
763269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "materials provided with the distribution. The name of the author may not\n"
764d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            + "be used to endorse or promote products derived from this software without\n"
765d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            + "specific prior written permission. \n"
766269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + " \n" + "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"
767269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"
768269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"
769269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n"
770269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n"
771269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"
772269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"
773269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"
774269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"
775269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke            + "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.";
776d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
777d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke    /**
778d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     * Starts as a standalone file server and waits for Enter.
779d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke     */
780d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke    public static void main(String[] args) {
781d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        System.out.println("NanoHTTPD 1.25 (C) 2001,2005-2011 Jarno Elonen and (C) 2010 Konstantinos Togias\n"
782d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                + "(Command line options: [-p port] [-d root-dir] [--licence])\n");
783d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
784d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        // Defaults
785d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        int port = 8080;
786d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        File wwwroot = new File(".").getAbsoluteFile();
787d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
788d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        // Show licence if requested
789d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        for (int i = 0; i < args.length; ++i)
790d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            if (args[i].equalsIgnoreCase("-p"))
791d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                port = Integer.parseInt(args[i + 1]);
792d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            else if (args[i].equalsIgnoreCase("-d"))
793d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                wwwroot = new File(args[i + 1]).getAbsoluteFile();
794d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            else if (args[i].toLowerCase().endsWith("licence")) {
795d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                System.out.println(LICENCE + "\n");
796d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                break;
797d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            }
798d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
7997b88819e82b89ac3476ce060903468076a73de8fPaul Hawke        NanoHTTPD server = new NanoHTTPD(port, wwwroot) {
8007b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            /**
8017b88819e82b89ac3476ce060903468076a73de8fPaul Hawke             * URL-encodes everything between "/"-characters. Encodes spaces as '%20' instead of '+'.
8027b88819e82b89ac3476ce060903468076a73de8fPaul Hawke             */
8037b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            private String encodeUri(String uri) {
8047b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                String newUri = "";
8057b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                StringTokenizer st = new StringTokenizer(uri, "/ ", true);
8067b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                while (st.hasMoreTokens()) {
8077b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    String tok = st.nextToken();
8087b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    if (tok.equals("/"))
8097b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        newUri += "/";
8107b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    else if (tok.equals(" "))
8117b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        newUri += "%20";
8127b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    else {
8137b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        try {
8147b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            newUri += URLEncoder.encode(tok, "UTF-8");
8157b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        } catch (UnsupportedEncodingException ignored) {
8167b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        }
817d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    }
8187b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                }
8197b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                return newUri;
8207b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            }
8217b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
8227b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            /**
8237b88819e82b89ac3476ce060903468076a73de8fPaul Hawke             * Serves file from homeDir and its' subdirectories (only). Uses only URI, ignores all headers and HTTP parameters.
8247b88819e82b89ac3476ce060903468076a73de8fPaul Hawke             */
8257b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            public Response serveFile(String uri, Map<String, String> header, File homeDir) {
8267b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                Response res = null;
8277b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
8287b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                // Make sure we won't die of an exception later
8297b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                if (!homeDir.isDirectory())
8300277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                    res = new Response(Response.HTTP_STATUS.INTERNAL_ERROR, MIME_PLAINTEXT, "INTERNAL ERRROR: serveFile(): given homeDir is not a directory.");
8317b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
8327b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                if (res == null) {
8337b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    // Remove URL arguments
8347b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    uri = uri.trim().replace(File.separatorChar, '/');
8357b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    if (uri.indexOf('?') >= 0)
8367b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        uri = uri.substring(0, uri.indexOf('?'));
8377b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
8387b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    // Prohibit getting out of current directory
8397b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    if (uri.startsWith("src/main") || uri.endsWith("src/main") || uri.contains("../"))
8400277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                        res = new Response(Response.HTTP_STATUS.FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: Won't serve ../ for security reasons.");
8417b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                }
8427b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
8437b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                File f = new File(homeDir, uri);
8447b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                if (res == null && !f.exists())
8450277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                    res = new Response(Response.HTTP_STATUS.NOT_FOUND, MIME_PLAINTEXT, "Error 404, file not found.");
8467b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
8477b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                // List the directory, if necessary
8487b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                if (res == null && f.isDirectory()) {
8497b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    // Browsers get confused without '/' after the
8507b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    // directory, send a redirect.
8517b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    if (!uri.endsWith("/")) {
8527b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        uri += "/";
8530277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                        res = new Response(Response.HTTP_STATUS.REDIRECT, MIME_HTML, "<html><body>Redirected: <a href=\"" + uri + "\">" + uri
8547b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                + "</a></body></html>");
8557b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        res.addHeader("Location", uri);
8567b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    }
8577b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
8587b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    if (res == null) {
8597b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        // First try index.html and index.htm
8607b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        if (new File(f, "index.html").exists())
8617b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            f = new File(homeDir, uri + "/index.html");
8627b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        else if (new File(f, "index.htm").exists())
8637b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            f = new File(homeDir, uri + "/index.htm");
8647b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            // No index file, list the directory if it is readable
8657b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        else if (f.canRead()) {
8667b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            String[] files = f.list();
8677b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            String msg = "<html><body><h1>Directory " + uri + "</h1><br/>";
8687b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
8697b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            if (uri.length() > 1) {
8707b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                String u = uri.substring(0, uri.length() - 1);
8717b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                int slash = u.lastIndexOf('/');
8727b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                if (slash >= 0 && slash < u.length())
8737b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    msg += "<b><a href=\"" + uri.substring(0, slash + 1) + "\">..</a></b><br/>";
8747b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            }
8757b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
8767b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            if (files != null) {
8777b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                for (int i = 0; i < files.length; ++i) {
8787b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    File curFile = new File(f, files[i]);
8797b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    boolean dir = curFile.isDirectory();
8807b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    if (dir) {
8817b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                        msg += "<b>";
8827b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                        files[i] += "/";
8837b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    }
8847b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
8857b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    msg += "<a href=\"" + encodeUri(uri + files[i]) + "\">" + files[i] + "</a>";
8867b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
8877b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    // Show file size
8887b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    if (curFile.isFile()) {
8897b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                        long len = curFile.length();
8907b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                        msg += " &nbsp;<font size=2>(";
8917b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                        if (len < 1024)
8927b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                            msg += len + " bytes";
8937b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                        else if (len < 1024 * 1024)
8947b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                            msg += len / 1024 + "." + (len % 1024 / 10 % 100) + " KB";
8957b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                        else
8967b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                            msg += len / (1024 * 1024) + "." + len % (1024 * 1024) / 10 % 100 + " MB";
8977b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
8987b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                        msg += ")</font>";
8997b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    }
9007b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    msg += "<br/>";
9017b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    if (dir)
9027b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                        msg += "</b>";
9037b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                }
9047b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            }
9057b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            msg += "</body></html>";
9060277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                            res = new Response(msg);
9077b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        } else {
9080277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                            res = new Response(Response.HTTP_STATUS.FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: No directory listing.");
9097b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        }
910d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    }
9117b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                }
9127b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
9137b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                try {
9147b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    if (res == null) {
9157b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        // Get MIME type from file name extension, if possible
9167b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        String mime = null;
9177b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        int dot = f.getCanonicalPath().lastIndexOf('.');
9187b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        if (dot >= 0)
9197b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            mime = MIME_TYPES.get(f.getCanonicalPath().substring(dot + 1).toLowerCase());
9207b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        if (mime == null)
9217b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            mime = MIME_DEFAULT_BINARY;
9227b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
9237b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        // Calculate etag
9247b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        String etag = Integer.toHexString((f.getAbsolutePath() + f.lastModified() + "" + f.length()).hashCode());
9257b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
9267b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        // Support (simple) skipping:
9277b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        long startFrom = 0;
9287b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        long endAt = -1;
9297b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        String range = header.get("range");
9307b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        if (range != null) {
9317b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            if (range.startsWith("bytes=")) {
9327b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                range = range.substring("bytes=".length());
9337b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                int minus = range.indexOf('-');
9347b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                try {
9357b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    if (minus > 0) {
9367b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                        startFrom = Long.parseLong(range.substring(0, minus));
9377b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                        endAt = Long.parseLong(range.substring(minus + 1));
9387b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    }
9397b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                } catch (NumberFormatException ignored) {
9407b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                }
9417b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            }
9427b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        }
9437b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
9447b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        // Change return code and add Content-Range header when skipping is requested
9457b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        long fileLen = f.length();
9467b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        if (range != null && startFrom >= 0) {
9477b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            if (startFrom >= fileLen) {
9480277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                                res = new Response(Response.HTTP_STATUS.RANGE_NOT_SATISFIABLE, MIME_PLAINTEXT, "");
9497b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                res.addHeader("Content-Range", "bytes 0-0/" + fileLen);
9507b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                res.addHeader("ETag", etag);
9517b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            } else {
9527b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                if (endAt < 0)
9537b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    endAt = fileLen - 1;
9547b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                long newLen = endAt - startFrom + 1;
9557b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                if (newLen < 0)
9567b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    newLen = 0;
9577b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
9587b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                final long dataLen = newLen;
9597b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                FileInputStream fis = new FileInputStream(f) {
9607b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    @Override
9617b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    public int available() throws IOException {
9627b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                        return (int) dataLen;
9637b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                    }
9647b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                };
9657b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                fis.skip(startFrom);
9667b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
9670277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                                res = new Response(Response.HTTP_STATUS.PARTIAL_CONTENT, mime, fis);
9687b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                res.addHeader("Content-Length", "" + dataLen);
9697b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                res.addHeader("Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen);
9707b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                res.addHeader("ETag", etag);
9717b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            }
9727b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        } else {
9737b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            if (etag.equals(header.get("if-none-match")))
9740277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                                res = new Response(Response.HTTP_STATUS.NOT_MODIFIED, mime, "");
9757b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            else {
9760277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                                res = new Response(Response.HTTP_STATUS.OK, mime, new FileInputStream(f));
9777b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                res.addHeader("Content-Length", "" + fileLen);
9787b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                                res.addHeader("ETag", etag);
9797b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                            }
9807b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                        }
981d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                    }
9827b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                } catch (IOException ioe) {
9830277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke                    res = new Response(Response.HTTP_STATUS.FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: Reading file failed.");
9847b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                }
985d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
9867b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                res.addHeader("Accept-Ranges", "bytes"); // Announce that the file server accepts partial content requestes
9877b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                return res;
9887b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            }
9897b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
9900277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke            public Response serve(String uri, METHOD method, Map<String, String> header, Map<String, String> parms, Map<String, String> files) {
9917b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                System.out.println(method + " '" + uri + "' ");
9927b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
9937b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                Iterator<String> e = header.keySet().iterator();
9947b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                while (e.hasNext()) {
9957b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    String value = e.next();
9967b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    System.out.println("  HDR: '" + value + "' = '" + header.get(value) + "'");
9977b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                }
9987b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                e = parms.keySet().iterator();
9997b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                while (e.hasNext()) {
10007b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    String value = e.next();
10017b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    System.out.println("  PRM: '" + value + "' = '" + parms.get(value) + "'");
1002d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke                }
10037b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                e = files.keySet().iterator();
10047b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                while (e.hasNext()) {
10057b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    String value = e.next();
10067b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                    System.out.println("  UPLOADED: '" + value + "' = '" + files.get(value) + "'");
10077b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                }
10087b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
10097b88819e82b89ac3476ce060903468076a73de8fPaul Hawke                return serveFile(uri, header, getRootDir());
10107b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            }
10117b88819e82b89ac3476ce060903468076a73de8fPaul Hawke        };
10127b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
10137b88819e82b89ac3476ce060903468076a73de8fPaul Hawke        try {
10147b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            server.start();
1015d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        } catch (IOException ioe) {
1016d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            System.err.println("Couldn't start server:\n" + ioe);
1017d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke            System.exit(-1);
1018d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        }
1019d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
1020d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        System.out.println("Now serving files in port " + port + " from \"" + wwwroot + "\"");
1021d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        System.out.println("Hit Enter to stop.\n");
1022d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke
1023d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        try {
10247b88819e82b89ac3476ce060903468076a73de8fPaul Hawke            System.in.read();
1025d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        } catch (Throwable ignored) {
1026d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke        }
10277b88819e82b89ac3476ce060903468076a73de8fPaul Hawke
10287b88819e82b89ac3476ce060903468076a73de8fPaul Hawke        server.stop();
1029d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke    }
1030269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke}
1031