Package pspdash

Source Code of pspdash.TinyWebServer$TinyWebThread$CGIPool

// PSP Dashboard - Data Automation Tool for PSP-like processes
// Copyright (C) 2003 Software Process Dashboard Initiative
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// The author(s) may be contacted at:
// OO-ALC/TISHD
// Attn: PSP Dashboard Group
// 6137 Wardleigh Road
// Hill AFB, UT 84056-5843
//
// E-Mail POC:  processdash-devel@lists.sourceforge.net

package pspdash;

import pspdash.data.DataRepository;
import pspdash.data.DoubleData;
import pspdash.data.ImmutableDoubleData;
import pspdash.data.StringData;
import java.net.*;
import java.io.*;
import java.util.*;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.*;

public class TinyWebServer extends Thread {

    ServerSocket serverSocket = null;
    ServerSocket dataSocket = null;
    Vector serverThreads = new Vector();
    URL [] roots = null;
    DataRepository data = null;

    Hashtable cgiLoaderMap = new Hashtable();
    Hashtable addOnLoaderMap = new Hashtable();
    Hashtable cgiCache = new Hashtable();
    MD5 md5 = new MD5();
    boolean allowRemoteConnections = false;
    private int port;
    private String startupTimestamp, startupTimestampHeader;

    public static final String PROTOCOL = "HTTP/1.0";
    public static final String DEFAULT_TEXT_MIME_TYPE =
        "text/plain; charset=iso-8859-1";
    public static final String DEFAULT_BINARY_MIME_TYPE =
        "application/octet-stream";
    public static final String SERVER_PARSED_MIME_TYPE =
        "text/x-server-parsed-html";
    public static final String CGI_MIME_TYPE = "application/x-httpd-cgi";
    public static final String TIMESTAMP_HEADER = "Dash-Startup-Timestamp";
    public static final String PACKAGE_ENV_PREFIX = "Dash_Package_";
    public static final String LINK_SUFFIX = ".link";
    public static final String LINK_MIME_TYPE = "text/x-server-shortcut";
    public static final String CGI_LINK_PREFIX = "class:";

    private static final DateFormat dateFormat =
                           // Tue, 05 Dec 2000 17:28:07 GMT
        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");

    private static InetAddress LOCAL_HOST_ADDR, LOOPBACK_ADDR;
    private static final Properties mimeTypes = new Properties();
    private static final Hashtable DEFAULT_ENV = new Hashtable();
    private static final String CRLF = "\r\n";
    private static final int SCAN_BUF_SIZE = 4096;
    private static final String DASH_CHARSET = "iso-8859-1";
    private static final String HEADER_CHARSET = DASH_CHARSET;
    private static String OUTPUT_CHARSET = DASH_CHARSET;

    static {
        try {
            DEFAULT_ENV.put("SERVER_SOFTWARE", "PSPDASH");
            DEFAULT_ENV.put("SERVER_NAME", "localhost");
            DEFAULT_ENV.put("GATEWAY_INTERFACE", "CGI/1.1");
            DEFAULT_ENV.put("SERVER_ADDR", "127.0.0.1");
            DEFAULT_ENV.put("PATH_INFO", "");
            DEFAULT_ENV.put("PATH_TRANSLATED", "");
            DEFAULT_ENV.put("REMOTE_HOST", "localhost");
            DEFAULT_ENV.put("REMOTE_ADDR", "127.0.0.1");

            dateFormat.setTimeZone(new SimpleTimeZone(0, "GMT"));
            mimeTypes.load(TinyWebServer.class
                           .getResourceAsStream("mime_types"));
        } catch (Exception e) { e.printStackTrace(); }
        try {
            LOCAL_HOST_ADDR = InetAddress.getLocalHost();
            LOOPBACK_ADDR   = InetAddress.getByName("127.0.0.1");
        } catch (UnknownHostException uhe) {}
    }

    private ClassLoader getParentClassLoader(String url) {
        if (!url.startsWith("jar:")) return null;
        int pos = url.lastIndexOf("!/Templates");
        if (pos == -1) return null;
        url = url.substring(0, pos+2);
        synchronized (addOnLoaderMap) {
            ClassLoader result = (ClassLoader) addOnLoaderMap.get(url);
            if (result == null) try {
                result = new AddOnClassLoader(url);
                addOnLoaderMap.put(url, result);
            } catch (Exception e) {}
            return result;
        }
    }

    private class AddOnClassLoader extends ClassLoader {
        URL base;
        public AddOnClassLoader(String path) {
            super();
            init(path);
        }
        protected AddOnClassLoader(String path, ClassLoader parent) {
            super(parent);
            init(path);
        }
        private void init(String path) {
            try {
                base = new URL(path);
            } catch (Exception e) {
                base = null;
            }
        }
        protected Class findClass(String name) throws ClassNotFoundException {
            if (name.startsWith("Templates."))
                throw new ClassNotFoundException(name);

            try {
                String filename = name.replace('.', '/') + ".class";
                URL classURL = new URL(base, filename);
                URLConnection conn = classURL.openConnection();
                conn.connect();

                byte [] defn = slurpContents(conn.getInputStream() , true);
                Class result = defineClass(name, defn, 0, defn.length);
                resolveClass(result);
                return result;
            } catch (Exception e) {
                throw new ClassNotFoundException(name);
            }
        }
        protected URL findResource(String name) {
            try {
                URL resourceURL = new URL(base, name);
                URLConnection conn = resourceURL.openConnection();
                conn.connect();
                return resourceURL;
            } catch (Exception e) {
                return null;
            }
        }
    }

    private class CGILoader extends AddOnClassLoader {
        public CGILoader(String path) {
            super(path);
        }
        public CGILoader(String path, ClassLoader parent) {
            super(path, parent);
        }
        public Class loadFromConnection(URLConnection conn)
            throws IOException, ClassFormatError
        {
            // Get the class name for the CGI script.  This is equal
            // to the name of the file, sans the ".class" extension.
            String className = conn.getURL().getFile();
            int beg = className.lastIndexOf('/');
            int end = className.indexOf('.', beg);
            className = className.substring(beg + 1, end);

            // If we have already loaded this class, fetch and return it
            Class result = findLoadedClass(className);
            if (result != null)
                return result;

            // Read the class definition from the connection
            byte [] defn = slurpContents(conn.getInputStream(), true);

            synchronized (this) {
                // check to see if someone else defined the class since
                // we last checked.
                result = findLoadedClass(className);
                if (result != null) return result;

                // Create a class from the definition read.
                result = defineClass(className, defn, 0, defn.length);
                resolveClass(result);
            }
            return result;
        }
        protected Class findClass(String name) throws ClassNotFoundException {
            if (name.indexOf('.') != -1)
                throw new ClassNotFoundException(name);
            return super.findClass(name);
        }
    }

    ResourcePool getCGIPool(String path) {
        return (ResourcePool) cgiCache.get(path);
    }

    private class TinyWebThread extends Thread {

        Socket clientSocket = null;
        InputStream inputStream = null;
        BufferedReader in = null;
        OutputStream outputStream = null;
        Writer headerOut = null;
        boolean isRunning = false;
        Exception exceptionEncountered = null;
        boolean headerRead = false;
        Map env = null;
        String outputCharset = OUTPUT_CHARSET;

        String uri, method, protocol, id, path, query;

        private class TinyWebThreadException extends Exception {};

        public TinyWebThread(Socket clientSocket) {
            try {
                this.clientSocket = clientSocket;
                this.inputStream = clientSocket.getInputStream();
                this.in = new BufferedReader
                    (new InputStreamReader(inputStream));
                this.outputStream = clientSocket.getOutputStream();
                this.headerOut = new BufferedWriter
                    (new OutputStreamWriter(outputStream, HEADER_CHARSET));
            } catch (IOException ioe) {
                this.inputStream = null;
            }
        }

        public TinyWebThread(String uri) {
            this.clientSocket = null;
            String request = "GET " + uri + " HTTP/1.0\r\n\r\n";
            this.inputStream = new ByteArrayInputStream(request.getBytes());
            this.in = new BufferedReader(new InputStreamReader(inputStream));
            this.outputStream = new ByteArrayOutputStream(1024);
            this.outputCharset = "UTF-8";
            try {
                this.headerOut = new BufferedWriter
                    (new OutputStreamWriter(outputStream, HEADER_CHARSET));
            } catch (UnsupportedEncodingException e) {
                // shouldn't happen.
            }
        }

        public byte[] getOutput() throws IOException {
            if (outputStream instanceof ByteArrayOutputStream) {
                run();
                if (exceptionEncountered instanceof IOException)
                    throw (IOException) exceptionEncountered;
                if (exceptionEncountered != null) {
                  IOException ioe = new IOException();
                  ioe.initCause(exceptionEncountered);
                    throw ioe;
                }

                return ((ByteArrayOutputStream) outputStream).toByteArray();

            } else
                return null;
        }

        public void dispose() {
            close();
            inputStream = null;
            outputStream = null;
            exceptionEncountered = null;
        }

        public synchronized void close() {
            if (isRunning)
                this.interrupt();
            serverThreads.remove(this);

            try {
                if (headerOut != null) { headerOut.flush(); headerOut.close(); }
                if (in  != null) in.close();
                if (clientSocket != null) clientSocket.close();
            } catch (IOException ioe) {}

            headerOut = null;
            in = null;
            clientSocket = null;
            env = null;
        }

        public void run() {

            if (inputStream != null) {
                isRunning = true;
                try {
                    handleRequest();
                } catch (TinyWebThreadException twte) {
                    if (exceptionEncountered == null)
                        exceptionEncountered = twte;
                }
                isRunning = false;
            }

            close();
        }

        private void handleRequest() throws TinyWebThreadException {
            String line = null;
            try {
                // read and process the header line
                line = readLine(inputStream);
                StringTokenizer tok = new StringTokenizer(line, " ");
                method   = tok.nextToken();
                uri      = tok.nextToken();
                protocol = tok.nextToken();

                // Check for a valid method
                if (!"GET".equals(method) && !"POST".equals(method))
                    sendError(501, "Not Implemented",
                              "Unsupported Request Method" );

                // break the uri into hierarchy path, file path, and
                // query string; place the results in the appropriate
                // object-global variables.
                parseURI(uri);

                // only accept localhost requests.
                checkIP();

                // serve up the request.
                serveRequest();

            } catch (NoSuchElementException nsee) {
                sendError( 400, "Bad Request", "No request found." );
            } catch (IOException ioe) {
                sendError( 500, "Internal Error", "IO Exception." );
            }
        }

        private void serveRequest() throws TinyWebThreadException, IOException
        {
            // open the requested file
            URLConnection conn = resolveURL(path);

            // decide what to do with the file based on its mime-type.
            String initial_mime_type =
                getMimeTypeFromName(conn.getURL().getFile());
            if (!Translator.isTranslating() &&
                SERVER_PARSED_MIME_TYPE.equals(initial_mime_type))
                servePreprocessedFile(conn, "text/html");
            else if (CGI_MIME_TYPE.equals(initial_mime_type))
                serveCGI(conn);
            else if (LINK_MIME_TYPE.equals(initial_mime_type))
                serveLink(conn);
            else
                servePlain(conn, initial_mime_type);
        }

        /** Break the URI into hierarchy path, file path, and query string.
         *
         * The results are placed into the object-global variables
         * "id", "path", and "query".
         *
         * URIs of the following forms are recognized (all may have
         * query strings appended): <PRE>
         *     /#####/regular/path
         *     /regular/path
         *     //regular/path
         *     /hierarchy/path//regular/path
         * </PRE> */
        private void parseURI(String uri) throws TinyWebThreadException {

            // extract the query string from the end.
            int pos = uri.indexOf('?');
            if (pos != -1) {
                query = uri.substring(pos + 1);
                uri = uri.substring(0, pos);
            }

            // remove "." and ".." directories from the uri.
            uri = canonicalizePath(uri);

            // ensure uri starts with a slash.
            if ( uri == null || !uri.startsWith("/") )
                sendError( 400, "Bad Request", "Bad filename." );

            // find the double slash that separates the id from the path.
            pos = uri.indexOf("//");
            if (pos >= 0) {
                id = uri.substring(0, pos);
                path = uri.substring(pos+2);
            } else try {
                 pos = uri.indexOf('/', 1);
                 id = uri.substring(1, pos);
                 Integer.parseInt(id);
                 path = uri.substring(pos + 1);
             } catch (Exception e) {
                 /* This block will be reached if the uri did not contain a
                  * second '/' character, or if the text between the initial
                  * and the second slash was not a number.  In these cases,
                  * we treat the uri as a simple file path, with no hierarchy
                  * path information.
                  */
                 id = "";
                 path = uri.substring(1);
             }
         }

         /** Resolve an absolute URL */
         private URLConnection resolveURL(String url)
             throws TinyWebThreadException
         {
             URLConnection result = TinyWebServer.this.resolveURL(url);
             if (result == null)
                 sendError(404, "Not Found", "File '" + url + "' not found.");
             return result;
         }

         /* Currently unused.
          * Resolve a relative URL
         private URLConnection resolveURL(String url, URL base)
             throws TinyWebThreadException
         {
             if (url.charAt(0) == '/')
                 return resolveURL(url.substring(1));

             try {
                 URL u = checkSafeURL(new URL(base, url));
                 if (u != null) {
                     URLConnection result = u.openConnection();
                     result.connect();
                     return result;
                 }
             } catch (IOException a) { }

             sendError(404, "Not Found", "File '" + url + "' not found.");
             return null;        // this line will never be reached.
         }
         */

         private void parseHTTPHeaders() throws IOException {
             buildEnvironment();

             if (headerRead) return;

             // Parse the headers on the original http request and add to
             // the cgi script environment.
             String line, header;
             StringBuffer text = new StringBuffer();
             int pos;
             while (null != (line = readLine(inputStream))) {
                 if (line.length() == 0) break;
                 header = parseHeader(line,text).toUpperCase().replace('-','_');

                 if (header.equals("CONTENT_TYPE") ||
                     header.equals("CONTENT_LENGTH"))
                     env.put(header, text.toString());
                 else
                     env.put("HTTP_" + header, text.toString());
             }
             headerRead = true;
         }


        /** parse name=value pairs in the body of a server shortcut,
         * and add them to the query string */
        private void parseLinkParameters(BufferedReader linkContents)
            throws IOException
        {
            StringBuffer queryString = new StringBuffer();
            String param, name, val;
            int equalsPos;

            // read each line of the file.
            while ((param = linkContents.readLine()) != null) {
                equalsPos = param.indexOf('=');
                // ignore empty lines and lines starting with '='
                if (equalsPos == 0 || param.length() == 0)
                    continue;
                else if (equalsPos == -1)
                    // if there is no value, append the name only.
                    queryString.append("&")
                        .append(URLEncoder.encode(param.trim()));
                else {
                    // extract and append name and value.
                    name = param.substring(0, equalsPos);
                    val = param.substring(equalsPos+1);
                    if (val.startsWith("=")) val = val.substring(1);
                    queryString.append("&").append(URLEncoder.encode(name))
                        .append("=").append(URLEncoder.encode(val));
                }
            }

            if (queryString.length() != 0) {
                // merge the newly constructed query parameters with
                // the existing query string.
                String existingQuery = (String) env.get("QUERY_STRING");
                if (existingQuery != null)
                    queryString.append("&").append(existingQuery);

                // save the resulting query string into the environment
                // for this thread.
                query = queryString.toString().substring(1);
                env.put("QUERY_STRING", query);
            }
        }


        /** Handle a server shortcut. */
        private void serveLink(URLConnection conn)
            throws IOException, TinyWebThreadException
        {
            BufferedReader linkContents = new BufferedReader
                (new InputStreamReader(conn.getInputStream()));
            String redirectLocation = linkContents.readLine();
            if (redirectLocation.indexOf('?') != -1 ||
                redirectLocation.indexOf("//") != -1)
                sendError(500, "Internal Error",
                          "Malformed server shortcut.");

            // Parse the headers and build the environment.
            parseHTTPHeaders();
            parseLinkParameters(linkContents);

            linkContents.close();

            if (redirectLocation.startsWith(CGI_LINK_PREFIX)) {
                String className = redirectLocation
                    .substring(CGI_LINK_PREFIX.length()).trim();
                serveCGI(getScript(conn, className));
            } else {
                try {
                    // resolve the shortcut target as a URL relative
                    // to the path of the server shortcut.  Although
                    // this isn't strictly necessary if the target is
                    // absolute, performing these steps anyway helps
                    // to ensure that the URL is well-formed.
                    URL contextURL = new URL("http://unimportant/" + path);
                    URL uriURL = new URL(contextURL, redirectLocation);
                    path = uriURL.getFile().substring(1);
                } catch (IOException ioe) {
                    sendError(500, "Internal Error",
                              "Malformed server shortcut.");
                }
                serveRequest();
            }
        }

        /** Handle a cgi-like http request. */
        private void serveCGI(URLConnection conn)
            throws IOException, TinyWebThreadException
        {
            serveCGI(getScript(conn));
        }


        /** Handle a cgi-like http request. */
        private void serveCGI(TinyCGI script)
            throws IOException, TinyWebThreadException
        {
            // Parse the headers and build the environment.
            parseHTTPHeaders();

            // Run the cgi script, and capture the results.
            OutputStream cgiOut = null;
            File tempFile = null;
            try {
                if (script == null)
                    sendError(500, "Internal Error", "Couldn't load script." );

                if (script instanceof TinyCGIHighVolume) {
                    tempFile = File.createTempFile("cgi", null);
                    cgiOut = new FileOutputStream(tempFile);
                } else {
                    cgiOut = new ByteArrayOutputStream();
                }

                script.service(inputStream, cgiOut, env);
            } catch (Exception cgie) {
                this.exceptionEncountered = cgie;
                if (tempFile != null) tempFile.delete();
                if (clientSocket == null) {
                    return;
                } else if (cgie instanceof TinyCGIException) {
                    TinyCGIException tce = (TinyCGIException) cgie;
                    sendError(tce.getStatus(), tce.getTitle(), tce.getText(),
                              tce.getOtherHeaders());
                } else {
                    StringWriter w = new StringWriter();
                    cgie.printStackTrace(new PrintWriter(w));
                    sendError(500, "CGI Error", "Error running script: " +
                              "<PRE>" + w.toString() + "</PRE>");
                }
            } finally {
                if (script != null) doneWithScript(script);
            }
            InputStream cgiResults = null;
            long totalSize = 0;
            if (tempFile == null) {
                byte [] results =
                    ((ByteArrayOutputStream) cgiOut).toByteArray();
                totalSize = results.length;
                cgiResults = new ByteArrayInputStream(results);
            } else {
                cgiOut.close();
                totalSize = tempFile.length();
                cgiResults = new FileInputStream(tempFile);
            }

            // Parse the headers generated by the cgi program.
            String contentType = null, statusString = "OK", line, header;
            StringBuffer otherHeaders = new StringBuffer();
            StringBuffer text = new StringBuffer();
            int status = 200;
            int headerLength = 0;
            while (true) {
                line = readLine(cgiResults, true);
                headerLength += line.length();

                // if the header line begins with a line termination char,
                if (line.charAt(0) == '\r' || line.charAt(0) == '\n')
                    break; // then we've encountered the end of the headers.

                header = parseHeader(line, text);

                if (header.toUpperCase().equals("STATUS")) {
                    // header value is of the form nnn xxxxxxx (a 3-digit
                    // status code, followed by an error string.
                    statusString = text.toString();
                    status = Integer.parseInt(statusString.substring(0, 3));
                    statusString = statusString.substring(4);
                }
                else if (header.toUpperCase().equals("CONTENT-TYPE"))
                    contentType = text.toString();
                else {
                    if (header.toUpperCase().equals("LOCATION"))
                        status = 302;
                    otherHeaders.append(header).append(": ")
                        .append(text.toString()).append(CRLF);
                }
            }

            // Success!! Send results back to the client.
            sendHeaders(status, statusString, contentType,
                        totalSize - headerLength,  -1,
                        otherHeaders.toString());
            byte[] buf = new byte[2048];
            int bytesRead;
            while ((bytesRead = cgiResults.read(buf)) != -1)
                outputStream.write(buf, 0, bytesRead);
            outputStream.flush();

            // clean up after ourselves.
            try {
                cgiResults.close();
                if (tempFile != null) tempFile.delete();
            } catch (IOException ioe) {}
        }

        /** Create an environment for use by a CGI script or a server
         *  preprocessed file */
        private void buildEnvironment() {
            if (env != null) return;

            // Create the environment for the cgi script.
            env = new HashMap(DEFAULT_ENV);
            env.put("SERVER_PROTOCOL", protocol);
            env.put("REQUEST_METHOD", method);
            env.put("PATH_INFO", id);
            if (id != null && id.startsWith("/")) {
                env.put("PATH_TRANSLATED", URLDecoder.decode(id));
                env.put("SCRIPT_PATH", id + "//" + path);
            } else {
                if (data != null) env.put("PATH_TRANSLATED", data.getPath(id));
                env.put("SCRIPT_PATH", "/" + id + "/" + path);
            }
            env.put("SCRIPT_NAME", "/" + path);
            env.put("REQUEST_URI", uri);
            env.put("QUERY_STRING", query);
            if (clientSocket != null) {
                env.put("REMOTE_PORT",
                        Integer.toString(clientSocket.getPort()));
                InetAddress addr = clientSocket.getInetAddress();
                env.put("REMOTE_HOST", addr.getHostName());
                env.put("REMOTE_ADDR", addr.getHostAddress());
                addr = clientSocket.getLocalAddress();
                env.put("SERVER_NAME", addr.getHostName());
                env.put("SERVER_ADDR", addr.getHostAddress());
            }
            env.put(TinyCGI.TINY_WEB_SERVER, TinyWebServer.this);
        }

        private class CGIPool extends ResourcePool {
            Class cgiClass;
            CGIPool(String name, Class c) throws IllegalArgumentException {
                super(name);
                if (!TinyCGI.class.isAssignableFrom(c))
                    throw new IllegalArgumentException
                        (c.getName() + " does not implement pspdash.TinyCGI");
                cgiClass = c;
            }
            protected Object createNewResource() {
                try {
                    return cgiClass.newInstance();
                } catch (Throwable t) {
                    return null;
                }
            }
        }

        /** Get an appropriate CGILoader for loading a class from the given
         * connection.
         */
        private CGILoader getLoader(URLConnection conn) {
            // All the cgi classes in a given directory are loaded by
            // a common classloader.  To find the classloader for this
            // class, we first extract the "directory" portion of the url
            // for this connection.
            String path = conn.getURL().toExternalForm();
            int end = path.lastIndexOf('/');
            path = path.substring(0, end+1);

            synchronized (cgiLoaderMap) {
                CGILoader result = (CGILoader) cgiLoaderMap.get(path);
                if (result == null) {
                    ClassLoader parent = getParentClassLoader(path);
                    if (parent == null)
                        result = new CGILoader(path);
                    else
                        result = new CGILoader(path, parent);
                    cgiLoaderMap.put(path, result);
                }
                return result;
            }
        }


        /** Get a TinyCGI script for a given uri path.
         * @param conn the URLConnection to the ".class" file for the script.
         *   TinyCGI scripts must be java classes in the root package (like
         *   the servlets API).
         * @return an instantiated TinyCGI script, or null on error.
         */
        private TinyCGI getScript(URLConnection conn) {
            return getScript(conn, null);
        }
        private TinyCGI getScript(URLConnection conn, String className) {
            CGIPool pool = null;
            synchronized (cgiCache) {
                pool = (CGIPool) cgiCache.get(path);
                if (pool == null) try {
                    CGILoader cgiLoader = getLoader(conn);
                    Class clz = null;
                    if (className == null)
                        clz = cgiLoader.loadFromConnection(conn);
                    else
                        clz = cgiLoader.loadClass(className);
                    pool = new CGIPool(path, clz);
                    cgiCache.put(path, pool);
                } catch (Throwable t) {
                    return null;
                }
            }
            return (TinyCGI) pool.get();
        }
        private void doneWithScript(Object script) {
            CGIPool pool = (CGIPool) cgiCache.get(path);
            if (pool != null)
                pool.release(script);
        }


        /** Parse an HTTP header (of the form "Header: value").
         *
         *  @param line The HTTP header line.
         *  @param value The value of the header found will be placed in
         *               this StringBuffer.
         *  @return The name of the header found.
         */
        private String parseHeader(String line, StringBuffer value) {
            int len = line.length();
            int pos = 0;
            while (pos < len  &&  ": \t".indexOf(line.charAt(pos)) == -1)
                pos++;
            String result = line.substring(0, pos);
            while (pos < len  &&  ": \t".indexOf(line.charAt(pos)) != -1)
                pos++;
            value.setLength(0);
            int end = line.indexOf('\r', pos);
            if (end == -1) end = line.indexOf('\n', pos);
            if (end == -1) end = line.length();
            value.append(line.substring(pos, end));
            return result;
        }


        /** Serve a plain HTTP request */
        private void servePlain(URLConnection conn, String mime_type)
            throws TinyWebThreadException, IOException
        {
            byte[] buffer = new byte[SCAN_BUF_SIZE];
            InputStream content = conn.getInputStream();
            int numBytes = -1;
            if (content == null)
                sendError( 500, "Internal Error", "Couldn't read file." );

            boolean translate =
                Translator.isTranslating() && !nonTranslatedPath(path);
            boolean preprocess = SERVER_PARSED_MIME_TYPE.equals(mime_type);

            if (mime_type == null ||
                (preprocess && translate) ||
                mime_type.startsWith("text/")) {

                PushbackInputStream pb = new PushbackInputStream
                    (content, SCAN_BUF_SIZE + 1);
                numBytes = pb.read(buffer);
                if (numBytes < 1)
                    sendError( 500, "Internal Error", "Couldn't read file." );
                pb.unread(buffer, 0, numBytes);
                content = pb;

                if (mime_type == null)
                    mime_type = getDefaultMimeType(buffer, numBytes);

                if ((preprocess && translate) ||
                     mime_type.startsWith("text/")) {
                    String scanBuf = new String
                        (buffer, 0, numBytes, DASH_CHARSET);
                    translate =
                        translate && mime_type.startsWith("text/html") &&
                        !containsNoTranslateTag(scanBuf);
                    preprocess =
                        preprocess || containsServerParseOverride(scanBuf);
                }
            }

            if (preprocess) {
                if (SERVER_PARSED_MIME_TYPE.equals(mime_type))
                    mime_type = "text/html";
                servePreprocessedFile(content, translate, mime_type);
            }

            else if (translate && mime_type.startsWith("text/html"))
                serveTranslatedFile(content, mime_type);

            else {
                discardHeader();
                sendHeaders(200, "OK", mime_type, conn.getContentLength(),
                            conn.getLastModified(), null);

                while (-1 != (numBytes = content.read(buffer))) {
                    outputStream.write(buffer, 0, numBytes);
                }
                outputStream.flush();
                content.close();
            }
        }

        private boolean containsServerParseOverride(String scanBuf) {
            return (scanBuf.indexOf(SERVER_PARSE_OVERRIDE) != -1);
        }
        private static final String SERVER_PARSE_OVERRIDE =
            "<!--#server-parsed";

        private boolean containsNoTranslateTag(String scanBuf) {
            return (scanBuf.indexOf(NO_TRANSLATE_TAG) != -1);
        }
        private static final String NO_TRANSLATE_TAG =
            "<!--#do-not-translate";

        private boolean nonTranslatedPath(String path) {
            return path.startsWith("help/");
        }

        private String setMimeTypeCharset(String type, String charset) {
            int pos = type.toLowerCase().indexOf("charset=");
            if (pos == -1)
                return type + "; charset=" + charset;
            return type.substring(pos+8) + charset;
        }


        /** Serve up a server-parsed html file. */
        private void servePreprocessedFile(URLConnection conn, String mimeType)
            throws TinyWebThreadException, IOException
        {
            String content = preprocessTextFile(conn.getInputStream(), false);
            byte[] bytes = content.getBytes(outputCharset);
            String contentType = setMimeTypeCharset(mimeType, outputCharset);
            sendHeaders(200, "OK", contentType, bytes.length, -1, null);
            outputStream.write(bytes);
        }


        /** Serve up a server-parsed html file. */
        private void servePreprocessedFile(InputStream in, boolean translate,
                                           String mimeType)
            throws TinyWebThreadException, IOException
        {
            String content = preprocessTextFile(in, translate);
            byte[] bytes = content.getBytes(outputCharset);
            String contentType = setMimeTypeCharset(mimeType, outputCharset);
            sendHeaders(200, "OK", contentType, bytes.length, -1, null);
            outputStream.write(bytes);
        }

        private String preprocessTextFile(InputStream in, boolean translate)
            throws TinyWebThreadException, IOException
        {
            byte[] rawContent = slurpContents(in, true);
            String content = new String(rawContent, DASH_CHARSET);
            if (translate)
                content = Translator.translate(content);

            parseHTTPHeaders();

            HTMLPreprocessor p =
                new HTMLPreprocessor(TinyWebServer.this, data, env);
            return p.preprocess(content);
        }

        private void serveTranslatedFile(InputStream content, String mime_type)
            throws IOException
        {
            discardHeader();
            Reader fileReader = new InputStreamReader(content, DASH_CHARSET);
            Reader translatedReader = Translator.translate(fileReader);
            Writer output = new OutputStreamWriter(outputStream, outputCharset);
            mime_type = setMimeTypeCharset(mime_type, outputCharset);

            sendHeaders(200, "OK", mime_type, -1, -1, null);

            char[] buf = new char[4096];
            int numChars;
            while (-1 != (numChars = translatedReader.read(buf))) {
                output.write(buf, 0, numChars);
            }
            output.flush();
            content.close();
        }


        /** read and discard the rest of the request header from inputStream */
        private void discardHeader() throws IOException {
            if (headerRead) return;

            String line;
            while (null != (line = readLine(inputStream)))
                if (line.length() == 0)
                    break;

            headerRead = true;
        }


        /** ensure that requests are originating from the local machine. */
        private void checkIP() throws TinyWebThreadException, IOException {
            // unconditionally allow internal requests.
            if (clientSocket == null) return;

            // unconditionally serve up items in the root directory
            // (This includes "style.css", "DataApplet.*", "data.js")
            // and the Images/ directory.
            if (path.indexOf('/') == -1 || path.startsWith("Images/")) return;

            // unconditionally serve requests that originate from the
            // local host.
            InetAddress remoteIP = clientSocket.getInetAddress();
            if (remoteIP.equals(LOOPBACK_ADDR) ||
                remoteIP.equals(LOCAL_HOST_ADDR)) return;

            parseHTTPHeaders();
            String path = (String) env.get("PATH_TRANSLATED");
            if (path == null) path = "";
            do {
                if (checkPassword(path)) return;
                path = chopPath(path);
            } while (path != null);

            if (! allowRemoteConnections)
                sendErrorOrAuth(403, "Forbidden", "Not accepting " +
                                "requests from remote IP addresses ." );
        }
        private String chopPath(String path) {
            if (path == null) return null;
            int slashPos = path.lastIndexOf('/');
            if (slashPos == -1)
                return null;
            else
                return path.substring(0, slashPos);
        }
        private boolean checkPassword(String path)
            throws TinyWebThreadException
        {
            String dataName = path + "/_Password_";
            Object value = data.getValue(dataName);
            if (value == null)
                return false;

            if (value instanceof DoubleData) {
                if (0 == ((DoubleData) value).getInteger())
                    sendErrorOrAuth(403, "Forbidden", "Not accepting " +
                                    "requests from remote IP addresses ." );
                else
                    return true;
            }

            if (value instanceof StringData) {
                String val = ((StringData) value).getString();
                sawPassword = true;

                if (getUserCredential() != null &&
                    val.indexOf(getUserCredential()) != -1) {
                    env.put("AUTH_USER", getAuthUser());
                    return true;
                }
                if (getGuestCredential() != null &&
                    val.indexOf(getGuestCredential()) != -1) {
                    env.put("AUTH_USER", "anonymous");
                    return true;
                }
            }

            return false;
        }
        private boolean sawPassword = false;
        private void sendErrorOrAuth(int status, String title, String text)
            throws TinyWebThreadException {
            if (sawPassword)
                sendError(401, "Unauthorized", "Authorization required.",
                          "WWW-Authenticate: Basic realm=\"Process " +
                          "Dashboard\""+CRLF);
            else
                sendError(status, title, text, null);
        }
        private String userCredential = null;
        private String guestCredential = null;
        private String wwwUser = null;
        private String getAuthUser() {
            authenticate(); return wwwUser; }
        private String getUserCredential() {
            authenticate(); return userCredential; }
        private String getGuestCredential() {
            authenticate(); return guestCredential; }
        private void authenticate() {
            if (wwwUser != null) return; // already authenticated
            String credentials = (String) env.get("HTTP_AUTHORIZATION");
            if (credentials == null) return; // no password given by client
            StringTokenizer tok = new StringTokenizer(credentials);
            try {
                tok.nextToken(); // "Basic"
                credentials = Base64.decode(tok.nextToken());
            } catch (Exception e) { return; }
            int colonPos = credentials.indexOf(':');
            if (colonPos == -1) return;
            wwwUser = credentials.substring(0, colonPos);
            String wwwPassword = credentials.substring(colonPos+1);

            String md5hash;
            synchronized (md5) {
                md5.Init();
                md5.Update(wwwPassword);
                md5hash = md5.asHex();
            }
            userCredential = wwwUser + ":" + md5hash;
            guestCredential = "*:" + md5hash;
        }


        /** Send an HTTP error page.
         *
         * @throws TinyWebThreadException automatically and unequivocally
         *    after printing the error page.  (This greatly simplifies
         *    TinyWebThread control logic.  Anytime an exception or
         *    other error is found, just call this method;  an error page
         *    will be generated, and then an exception will be thrown.
         *    TinyWebThreadExceptions are caught in only one place, at
         *    the top level run() method.)
         */
        private void sendError(int status, String title, String text )
            throws TinyWebThreadException {
            sendError(status, title, text, null);
        }
        private void sendError(int status, String title, String text,
                               String otherHeaders )
            throws TinyWebThreadException
        {
            TinyWebThreadException result = new TinyWebThreadException();
            try {
                if (exceptionEncountered == null)
                    exceptionEncountered = result;
                discardHeader();
                sendHeaders( status, title, "text/html", -1, -1, otherHeaders);
                headerOut.write("<HTML><HEAD><TITLE>" + status + " " + title +
                                "</TITLE></HEAD>\n<BODY BGCOLOR=\"#cc9999\"><H4>" +
                                status + " " + title + "</H4>\n" +
                                text + "\n" + "</BODY></HTML>\n");
                headerOut.flush();
            } catch (IOException ioe) {
            }
            throw result;
        }

        private boolean headersSent = false;
        private void sendHeaders(int status, String title, String mimeType,
                                  long length, long mod, String otherHeaders )
            throws IOException
        {
            if (headersSent) return;

            headersSent = true;

            Date now = new Date();

            headerOut.write(PROTOCOL + " " + status + " " + title + CRLF);
            headerOut.write("Server: localhost" + CRLF);
            headerOut.write("Date: " + dateFormat.format(now) + CRLF);
            if (mimeType != null)
                headerOut.write("Content-Type: " + mimeType + CRLF);
            // headerOut.write("Accept-Ranges: bytes" + CRLF);
            if (mod > 0)
                headerOut.write("Last-Modified: " +
                                dateFormat.format(new Date(mod)) + CRLF);
            if (length >= 0)
                headerOut.write("Content-Length: " + length + CRLF);
            headerOut.write(startupTimestampHeader + CRLF);
            if (otherHeaders != null)
                headerOut.write(otherHeaders);
            headerOut.write("Connection: close" + CRLF + CRLF);
            headerOut.flush();
        }

        private String getMimeTypeFromName(String name) {
            // locate file extension and lookup associated mime type.
            int pos = name.lastIndexOf('.');
            if (pos >= 0) {
                String suffix = name.substring(pos).toLowerCase();
                if (suffix.equals(".class") &&
                    name.indexOf("/IE/") == -1 &&
                    name.indexOf("/NS/") == -1)
                    // Eventually, we may want a better method of deciding
                    // between cgi scripts and other class files.  Perhaps
                    // a special ID (like 0) can flag an uninterpreted .class
                    // file?
                    return CGI_MIME_TYPE;
                else
                    return (String) mimeTypes.get(suffix);
            } else
                return null;
        }

        /** Check to see if the data is text or binary, and return the
         *  appropriate default mime type. */
        private String getDefaultMimeType(byte [] buffer, int numBytes)
        {
            while (numBytes-- > 0)
                if (Character.isISOControl((char) buffer[numBytes]) &&
                    "\t\r\n\f".indexOf((char) buffer[numBytes]) == -1)
                    return DEFAULT_BINARY_MIME_TYPE;

            return DEFAULT_TEXT_MIME_TYPE;
        }
    }

    private boolean isDirectory(URL u) {
        String urlString = u.toString();
        if (!urlString.startsWith("file:/")) return false;
        String filename = urlString.substring(5);
        filename = URLDecoder.decode(filename);
        File file = new File(filename);
        return file.isDirectory();
    }

    /** Canonicalize a path through the removal of directory changes
     * made by occurences of &quot;..&quot; and &quot;.&quot;.
     *
     * @return a canonical path, or null on error.
     */
    private static String canonicalizePath(String path) {
        if (path == null) return null;
        path = path.trim();

        int pos, beg;
        while (true) {

            if (path.startsWith("../") || path.startsWith("/../"))
                return null;

            else if (path.startsWith("./"))
                path = path.substring(2);

            else if ((pos = path.indexOf("/./")) != -1)
                path = path.substring(0, pos) + path.substring(pos+2);

            else if (path.endsWith("/."))
                path = path.substring(0, path.length()-2);

            else if ((pos = path.indexOf("/../", 1)) != -1) {
                beg = path.lastIndexOf('/', pos-1);
                if (beg == -1)
                    path = path.substring(pos+4);
                else
                    path = path.substring(0, beg) + path.substring(pos+3);

            } else if (path.endsWith("/..")) {
                beg = path.lastIndexOf('/', path.length()-4);
                if (beg == -1)
                    return null;
                else
                    path = path.substring(0, beg+1);

            } else
                return path;
        }
    }

    private URLConnection resolveURL(String url) {
        url = canonicalizePath(url);
        if (url == null) return null;

        URL u;
        URLConnection result;
        for (int i = 0;  i < roots.length;  i++) try {
            u = new URL(roots[i], url);

            // don't accept resolved URLs that point to a directory
            if (isDirectory(u)) continue;

            // System.out.println("trying url: " + u);
            result = u.openConnection();
            // System.out.println("connection opened.");
            result.connect();
            // System.out.println("connection connected.");
            // System.out.println("Using URL: " + u);
            return result;
        } catch (IOException ioe) { }

        if (!url.endsWith(LINK_SUFFIX))
            return resolveURL(url + LINK_SUFFIX);

        return null;
    }

    /** Calculate the user credential that would work for an http
     * Authorization field.
     */
    public static String calcCredential(String user, String password) {
        return "Basic " + Base64.encode(user + ":" + password);
    }


    /** Save a password setting in the data repository.
     *
     * Adjusts the password settings for the given prefix.
     * Normally, adds the username/password pair to the password table,
     *     or changes the existing password for that username.
     * If user is null and password is non-null, discards all password
     *     information and marks this node as "forbidden".
     * If user is null and password is null, discards all password
     *     information and marks this node as "unprotected".
     */
    static void setPassword(DataRepository data, String prefix,
                                   String user, String password) {
        String dataName = data.createDataName(prefix,  "_Password_");

        if (user == null) {
            DoubleData val;
            if (password == null)
                val = ImmutableDoubleData.TRUE;
            else
                val = ImmutableDoubleData.FALSE;
            data.putValue(dataName, val);
            return;
        }


        Object val = data.getSimpleValue(dataName);
        HashMap passwords = new HashMap();
        if (val instanceof StringData) try {
            StringTokenizer tok = new StringTokenizer
                (((StringData) val).format(), ";");
            while (tok.hasMoreTokens()) {
                String credential = tok.nextToken();
                int colonPos = credential.indexOf(':');
                String credUser = credential.substring(0, colonPos);
                String passHash = credential.substring(colonPos+1);
                passwords.put(credUser, passHash);
            }
        } catch (Exception e) { e.printStackTrace(); }

        MD5 md5 = new MD5();
        md5.Init();
        md5.Update(password);
        passwords.put(user, md5.asHex());

        StringBuffer passwordList = new StringBuffer();
        Iterator i = passwords.entrySet().iterator();
        Map.Entry e;
        while (i.hasNext()) {
            e = (Map.Entry) i.next();
            passwordList.append(e.getKey()).append(":")
                .append(e.getValue()).append(";");
        }
        data.putValue(dataName, StringData.create(passwordList.toString()));
    }


    /** Encode HTML entities in the given string, and return the result. */
    public static String encodeHtmlEntities(String str) {
        str = StringUtils.findAndReplace(str, "&""&amp;");
        str = StringUtils.findAndReplace(str, "<""&lt;");
        str = StringUtils.findAndReplace(str, ">""&gt;");
        str = StringUtils.findAndReplace(str, "\"", "&quot;");
        return str;
    }

    public static String urlEncodePath(String path) {
        path = URLEncoder.encode(path);
        path = StringUtils.findAndReplace(path, "%2F", "/");
        path = StringUtils.findAndReplace(path, "%2f", "/");
        return path;
    }

    public static String getHostName() {
        String result = Settings.getVal("http.hostname");
        if (result == null || result.length() == 0) try {
            result = InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException uhe) {
            result = "localhost";
        }
        return result;
    }

    /** Utility routine: slurp an entire file from an InputStream. */
    public static byte[] slurpContents(InputStream in, boolean close)
        throws IOException
    {
        byte [] result = null;
        ByteArrayOutputStream slurpBuffer = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = in.read(buffer)) != -1)
            slurpBuffer.write(buffer, 0, bytesRead);
        result = slurpBuffer.toByteArray();
        if (close) try { in.close(); } catch (IOException ioe) {}
        return result;
    }


    /** Utility routine: readLine from an InputStream.
     *
     * This is needed because the only readLine method in the Java library
     * is in the BufferedReader class.  A BufferedReader will likely grab
     * more bytes than we necessarily want it to.
     *
     * Although this method is not performing any character encoding,
     * Hopefully we're okay because we're just parsing plaintext HTTP headers.
     */
    static String readLine(InputStream in) throws IOException {
        return readLine(in, false);
    }
    static String readLine(InputStream in, boolean keepCRLF)
        throws IOException
    {
        StringBuffer result = new StringBuffer();
        int c;
        while ((c = in.read()) != -1) {
            if (c == '\n') {
                if (keepCRLF) result.append((char) c);
                break;
            } else if (c == '\r') {
                if (keepCRLF) result.append((char) c);
            } else {
                result.append((char) c);
            }
        }

        return result.toString();
    }

    /** Utility routine: readLine from a byte array. The carraige return
     * and linefeed that terminate the line are returned as part of the
     * final result.
     *
     * Although this method is not performing any character encoding,
     * Hopefully we're okay because we're just parsing plaintext HTTP headers.
     */
    static String readLine(byte[] buf, int beg) throws IOException {
        int p = beg;
        // find an initial sequence of non-line-terminating charaters
        while (p < buf.length && buf[p] != '\r' && buf[p] != '\n') p++;
        // skip over up to two line termination characters.
        if (p < buf.length && buf[p] == '\r') p++;
        if (p < buf.length && buf[p] == '\n') p++;
        return new String(buf, beg, p-beg);
    }

    /** Perform an internal http request for the caller.
     *
     * @param uri the absolute uri of a resource on this server (e.g.
     *     <code>/0980/help/about.htm?foo=bar</code>)
     * @param skipHeaders if true, the generated response headers are discarded
     * @return the response generated by performing the http request.
     */
    public byte[] getRequest(String uri, boolean skipHeaders)
        throws IOException
    {
        if (internalRequestNesting > 50)
            throw new IOException("Infinite recursion - aborting.");

        synchronized(this) { internalRequestNesting++; }
        TinyWebThread t = new TinyWebThread(uri);
        byte [] result = null;
        try {
            result = t.getOutput();
        } finally {
            synchronized(this) { internalRequestNesting--; }
            if (t != null) t.dispose();
        }

        if (!skipHeaders)
            return result;
        else {
            int a=0, b=1, c=2, d=3;
            do {
                if (result[a] == '\r' && result[b] == '\n' &&
                    result[c] == '\r' && result[d] == '\n')
                    break;
                a++; b++; c++; d++;
            } while (d < result.length);
            byte [] contents = new byte[result.length - d - 1];
            System.arraycopy(result, d+1, contents, 0, contents.length);
            return contents;
        }
    }
    private volatile int internalRequestNesting = 0;

    /** Perform an internal http request for the caller.
     * @param context the uri of an original request within this web server
     * @param uri a uri to fetch the contents of.  If it does not begin with
     *     a slash, it will be interpreted relative to <code>context</code>.
     * @param skipHeaders if true, the generated response headers are discarded
     * @return the response generated by performing the http request.
     */
    public byte[] getRequest(String context, String uri, boolean skipHeaders)
        throws IOException
    {
        if (!uri.startsWith("/")) {
            URL contextURL = new URL("http://unimportant" + context);
            URL uriURL = new URL(contextURL, uri);
            uri = uriURL.getFile();
        }
        return getRequest(uri, skipHeaders);
    }

    /** Perform an internal http request and return raw results.
     *
     * Server-parsed HTML files are returned verbatim, and
     * cgi scripts are returned as binary streams.
     */
    public byte[] getRawRequest(String uri)
        throws IOException
    {
        try {
            if (uri.startsWith("/"))
                uri = uri.substring(1);
            URLConnection conn = resolveURL(uri);
            if (conn == null) return null;

            InputStream in = conn.getInputStream();
            byte[] result = slurpContents(in, true);
            return result;
        } catch (IOException ioe) {
            return null;
        }
    }

    /** Clear the classloader caches, so classes will be reloaded.
     */
    public void clearClassLoaderCaches() {
        addOnLoaderMap.clear();
        cgiLoaderMap.clear();
        cgiCache.clear();
    }

    private void writePackagesToDefaultEnv() {
        Iterator i = DEFAULT_ENV.keySet().iterator();
        while (i.hasNext())
            if (((String) i.next()).startsWith(PACKAGE_ENV_PREFIX))
                i.remove();
        i = TemplateLoader.getPackages().iterator();
        while (i.hasNext()) {
            DashPackage pkg = (DashPackage) i.next();
            DEFAULT_ENV.put(PACKAGE_ENV_PREFIX + pkg.id, pkg.version);
        }
    }

    /** Parse the HTTP headers in text, and put them into the dest map.
     *  Returns the number of bytes of header information found and parsed,
     *  so the body of the HTTP message will begin at that char in text.
     *
     * @param text an HTTP message
     * @param dest a Map where the parsed headers should be stored. The keys
     * in the map will be field names, converted to upper case.  The values
     * will be field values.  If a given header is repeated, the values will
     * be concatenated into a comma separated list, as suggested in RFC2616.
     * @return the number of bytes parsed out of text
     *
     * This isn't 100% compliant with RFC2616; it doesn't allow header values
     * to be split across multiple lines.
    public int getHeaders(byte [] text, Map dest) {
        String line, header, oldVal;
        StringBuffer value = new StringBuffer();
        int pos = 0;
        while (pos < text.length) {
            line = readLine(text, pos);
            pos += line.length();

            // if the header line begins with a line termination char,
            if (line.length() == 0 ||
                line.charAt(0) == '\r' || line.charAt(0) == '\n')
                break; // then we've encountered the end of the headers.

            header = parseHeader(line, value).toUpperCase();
            oldVal = (String) dest.get(header);
            if (oldVal == null)
                dest.put(header, value.toString());
            else
                dest.put(header, oldVal + "," + value);
        }
        return pos;
    }
     */

    /** Return the number of the port this server is listening on. */
    public int getPort()         { return port; }
    /** Return the socket we opened for data connections. */
    ServerSocket getDataSocket() { return dataSocket; }
    /** Return the startup timestamp for this server. */
    public String getTimestamp() { return startupTimestamp; }

    private void init(int port, URL [] roots) throws IOException
    {
        this.roots = roots;
        startupTimestamp = Long.toString((new Date()).getTime());
        startupTimestampHeader = TIMESTAMP_HEADER + ": " + startupTimestamp;

        InetAddress listenAddress = null;
        if ("never".equalsIgnoreCase(Settings.getVal("http.allowRemote")))
            listenAddress = LOOPBACK_ADDR;

        while (serverSocket == null) try {
            dataSocket = new ServerSocket(port-1, 50, listenAddress);
            serverSocket = new ServerSocket(port, 50, listenAddress);
        } catch (IOException ioex) {
            if (dataSocket != null) {
                try { dataSocket.close(); } catch (IOException ioe) {}
                dataSocket = null;
            }
            if (serverSocket != null) {
                try { serverSocket.close(); } catch (IOException ioe) {}
                serverSocket = null;
            }
            port += 2;
        }
        this.port = port;
        DEFAULT_ENV.put("SERVER_PORT", Integer.toString(port));
        writePackagesToDefaultEnv();

        String charsetName = Settings.getVal("http.charset");
        if (charsetName != null && charsetName.length() > 0) try {
            "test".getBytes(charsetName);
            OUTPUT_CHARSET = charsetName;
            TinyCGIBase.setDefaultCharset(OUTPUT_CHARSET);
        } catch (UnsupportedEncodingException uee) {}
    }

    /**
     * Run a tiny web server on the given port, serving up resources
     * out of the given package within the class path.
     *
     * Serving up resources out of the classpath seems like a nice
     * idea, since it allows html pages, etc to be JAR-ed up and
     * invisible to the user.
     */
    TinyWebServer(int port, String path) throws IOException
    {
        if (path == null || path.length() == 0)
            throw new IOException("Path must be specified");

        if (path.startsWith("/")) path = path.substring(1);
        if (!path.endsWith("/"))  path = path + "/";
        Enumeration e = getClass().getClassLoader().getResources(path);
        Vector v = new Vector();
        while (e.hasMoreElements())
            v.addElement(e.nextElement());
        int i = v.size();
        URL [] roots = new URL[i];
        while (i-- > 0)
            roots[i] = (URL) v.elementAt(i);

        init(port, roots);
    }

    /**
     * Run a tiny web server on the given port, serving up files out
     * of the given directory.
     */
    TinyWebServer(String directoryToServe, int port)
        throws IOException
    {
        File rootDir = new File(directoryToServe);
        if (!rootDir.isDirectory())
            throw new IOException("Not a directory: " + directoryToServe);

        URL [] roots = new URL[1];
        roots[0] = rootDir.toURL();

        init(port, roots);
    }

    /**
     * Run a tiny web server on the given port, serving up resources
     * out of the given list of template search URLs.
     */
    TinyWebServer(int port, URL [] roots) throws IOException {
        init(port, roots);
    }


    void setRoots(URL [] roots) {
        this.roots = roots;
        clearClassLoaderCaches();
        writePackagesToDefaultEnv();
    }
    void setProps(PSPProperties props) {
        if (props == null)
            DEFAULT_ENV.remove(TinyCGI.PSP_PROPERTIES);
        else
            DEFAULT_ENV.put(TinyCGI.PSP_PROPERTIES, props);
    }
    void setData(DataRepository data) {
        this.data = data;
        if (data == null)
            DEFAULT_ENV.remove(TinyCGI.DATA_REPOSITORY);
        else
            DEFAULT_ENV.put(TinyCGI.DATA_REPOSITORY, data);
    }
    void setCache(ObjectCache cache) {
        if (cache == null)
            DEFAULT_ENV.remove(TinyCGI.OBJECT_CACHE);
        else
            DEFAULT_ENV.put(TinyCGI.OBJECT_CACHE, cache);
    }
    void allowRemoteConnections(String setting) {
        this.allowRemoteConnections = "true".equalsIgnoreCase(setting);

        /* in the future, if better remote host filtering is desired,
         * this would be the place to put it.  For example instead of
         * "true", meaning "allow connections from any host", a user
         * might be able to supply a comma-separated list of hostnames.
         * Just add logic here to parse the setting, and logic in the
         * checkIP() method to act in accordance with the settings. */
    }


    private volatile boolean isRunning;


    public void run() {
        acceptRequests(serverSocket);
    }

    /** handle http requests. */
    protected void acceptRequests(ServerSocket serverSocket) {
        Socket clientSocket = null;
        TinyWebThread serverThread = null;

        if (serverSocket == null) return;

        isRunning = true;

        while (isRunning) try {
            clientSocket = serverSocket.accept();

            serverThread = new TinyWebThread(clientSocket);
            serverThreads.addElement(serverThread);
            serverThread.start();

        } catch (IOException e) { }

        while (serverThreads.size() > 0) {
            serverThread = (TinyWebThread) serverThreads.remove(0);
            serverThread.close();
        }

        close(serverSocket);
    }

    private class SecondaryServerSocket extends Thread {
        ServerSocket secondaryServerSocket;
        public SecondaryServerSocket(int port) throws IOException {
            // create a server socket that listens on the specified port.
            // bind to the same inet address as the "main" server socket.
            InetAddress listenAddress = serverSocket.getInetAddress();
            secondaryServerSocket = new ServerSocket(port, 50, listenAddress);
            dataSocket = new ServerSocket(port-1, 50, listenAddress);

            setDaemon(true);
        }
        public void run() {
            acceptRequests(secondaryServerSocket);
            secondaryServerSocket = null;
        }
    }


    /** Start listening for connections on an additional port */
    void addExtraPort(int port) throws IOException {
        SecondaryServerSocket s = new SecondaryServerSocket(port);
        secondaryServerSockets.add(s);
        s.start();
        this.port = port;
    }
    private Vector secondaryServerSockets = new Vector();



    /** Stop the web server. */
    public void quit() {
        isRunning = false;
        // interrupt the main server thread.
        this.interrupt();
        close(this.serverSocket);
        // interrupt any secondary server threads.
        Iterator i = secondaryServerSockets.iterator();
        while (i.hasNext()) ((Thread) i.next()).interrupt();

        this.serverSocket = null;
    }

    private synchronized void close(ServerSocket serverSocket) {
        if (serverSocket != null) try {
            serverSocket.close();
        } catch (IOException e2) {}
    }

    /** Run a web server on port 8000.  the first arg must name the
     *  directory to serve
    public static void main(String [] args) {
        try {
            InetAddress host = InetAddress.getLocalHost();
            System.out.println("TinyWeb starting " +
                               dateFormat.format(new Date()) + " on " +
                               host);
            (new TinyWebServer(args[0], 8000)).run();
        } catch (IOException ioe) {
            System.err.println(ioe);
        }
    }
    */
TOP

Related Classes of pspdash.TinyWebServer$TinyWebThread$CGIPool

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
cript','//www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-20639858-1', 'auto'); ga('send', 'pageview');