Files moved up from the old src directory.
roberts [Sun, 31 Jan 1999 02:45:49 +0000 (02:45 +0000)]
Moved Files:
FCGIInterface.java FCGIInputStream.java FCGIGlobalDefs.java
FCGIMessage.java FCGIOutputStream.java FCGIRequest.java

java/FCGIGlobalDefs.java [new file with mode: 0644]
java/FCGIInputStream.java [new file with mode: 0644]
java/FCGIInterface.java [new file with mode: 0644]
java/FCGIMessage.java [new file with mode: 0644]
java/FCGIOutputStream.java [new file with mode: 0644]
java/FCGIRequest.java [new file with mode: 0644]

diff --git a/java/FCGIGlobalDefs.java b/java/FCGIGlobalDefs.java
new file mode 100644 (file)
index 0000000..cd0fa8a
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * @(#)FCGIGlobalDefs.java
+ *
+ *
+ *      FastCGi compatibility package Interface
+ *
+ *
+ *  Copyright (c) 1996 Open Market, Inc.
+ *
+ * See the file "LICENSE.TERMS" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * $Id: FCGIGlobalDefs.java,v 1.1 1999/01/31 02:45:51 roberts Exp $
+ */
+
+/* This class contains FCGI global definitions corresponding to
+ * the #defs in the C version.
+ */
+import java.io.PrintStream;
+
+public abstract class FCGIGlobalDefs {
+
+    public static final int def_FCGIMaxLen = 0xffff;
+    /*
+    * Define Length of FCGI message bodies in bytes
+    */
+    public static final int def_FCGIHeaderLen   = 8;
+    public static final int def_FCGIEndReqBodyLen   = 8;
+    public static final int def_FCGIBeginReqBodyLen = 8;
+    public static final int def_FCGIUnknownBodyTypeBodyLen = 8;
+    /*
+    * Header defines
+    */
+    public static int def_FCGIVersion1      = 1;
+    /* FCGI Record Types */
+    public static final int def_FCGIBeginRequest    = 1;
+    public static final int def_FCGIAbortRequest    = 2;
+    public static final int def_FCGIEndRequest  = 3;
+    public static final int def_FCGIParams      = 4;
+    public static final int def_FCGIStdin       = 5;
+    public static final int def_FCGIStdout      = 6;
+    public static final int def_FCGIStderr      = 7;
+    public static final int def_FCGIData        = 8;
+    public static final int def_FCGIGetValues   = 9;
+    public static final int def_FCGIGetValuesResult = 10;
+    public static final int def_FCGIUnknownType     = 11;
+    public static final int def_FCGIMaxType = def_FCGIUnknownType;
+    /* Request ID Values */
+    public static final int def_FCGINullRequestID   = 0;
+    /*
+    * Begin Request defines
+    */
+    /* Mask flags */
+    public static int def_FCGIKeepConn      = 1;
+    /* Roles */
+    public static final int def_FCGIResponder   = 1;
+    public static final int def_FCGIAuthorizer  = 2;
+    public static final int def_FCGIFilter      = 3;
+    /*
+    * End Request defines
+    */
+    /* Protocol status */
+    public static final int def_FCGIRequestComplete = 0;
+    public static final int def_FCGICantMpxConn = 1;
+    public static final int def_FCGIOverload    = 2;
+    public static final int def_FCGIUnknownRole = 3;
+    /*
+    * Get Values, Get Values Results  defines
+    */
+    public static final String def_FCGIMaxConns = "FCGI_MAX_CONNS";
+    public static final String def_FCGIMaxReqs  = "FCGI_MAX_REQS";
+    public static final String def_FCGIMpxsConns    = "FCGI_MPXS_CONNS";
+    /*
+    * Return codes for Process* functions
+    */
+    public static final int def_FCGIStreamRecord    = 0;
+    public static final int def_FCGISkip        = 1;
+    public static final int def_FCGIBeginRecord = 2;
+    public static final int def_FCGIMgmtRecord = 3;
+    /*
+    * Error Codes
+    */
+    public static final int def_FCGIUnsupportedVersion = -2;
+    public static final int def_FCGIProtocolError   = -3;
+    public static final int def_FCGIParamsError = -4;
+    public static final int def_FCGICallSeqError    = -5;
+}
diff --git a/java/FCGIInputStream.java b/java/FCGIInputStream.java
new file mode 100644 (file)
index 0000000..4c2a1cc
--- /dev/null
@@ -0,0 +1,380 @@
+/*
+ * @(#)FCGIInputStream.java
+ *
+ *      FastCGi compatibility package Interface
+ *
+ *
+ *  Copyright (c) 1996 Open Market, Inc.
+ *
+ * See the file "LICENSE.TERMS" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * $Id: FCGIInputStream.java,v 1.1 1999/01/31 02:45:49 roberts Exp $
+ */
+
+import java.io.*;
+import  FCGIRequest;
+import  FCGIGlobalDefs;
+
+/**
+ * This stream manages buffered reads of FCGI messages.
+ */
+public class FCGIInputStream extends InputStream {
+
+    /* Stream vars */
+
+    public int rdNext;
+    public int stop;
+    public boolean isClosed;
+
+    /* require methods to set, get and clear */
+    private int errno;
+    private Exception errex;
+
+    /* data vars */
+
+    public byte buff[];
+    public int buffLen;
+    public int buffStop;
+    public int type;
+    public int contentLen;
+    public int paddingLen;
+    public boolean skip;
+    public boolean eorStop;
+    public FCGIRequest request;
+
+    public InputStream in;
+
+
+    /**
+    * Creates a new input stream to manage fcgi prototcol stuff
+    * @param in the input stream  bufLen  length of buffer streamType
+    */
+    public FCGIInputStream(FileInputStream inStream, int bufLen,
+        int streamType,
+        FCGIRequest inReq) {
+
+        in = inStream;
+        buffLen = Math.min(bufLen,FCGIGlobalDefs.def_FCGIMaxLen);
+        buff = new byte[buffLen];
+        type = streamType;
+        stop = rdNext = buffStop = 0;
+        isClosed = false;
+        contentLen = 0;
+        paddingLen = 0;
+        skip = false;
+        eorStop = false;
+        request = inReq;
+
+    }
+    /**
+    * Reads a byte of data. This method will block if no input is
+    * available.
+    * @return  the byte read, or -1 if the end of the
+    *      stream is reached.
+    * @exception IOException If an I/O error has occurred.
+    */
+    public int read() throws IOException {
+        if (rdNext != stop) {
+            return buff[rdNext++];
+        }
+        if (isClosed){
+            return -1;
+        }
+        fill();
+        if (rdNext != stop){
+            return buff[rdNext++];
+        }
+        return -1;
+    }
+    /**
+    * Reads into an array of bytes.  This method will
+    * block until some input is available.
+    * @param b the buffer into which the data is read
+    * @return  the actual number of bytes read, -1 is
+    *      returned when the end of the stream is reached.
+    * @exception IOException If an I/O error has occurred.
+    */
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    /**
+    * Reads into an array of bytes.
+    * Blocks until some input is available.
+    * @param b the buffer into which the data is read
+    * @param off the start offset of the data
+    * @param len the maximum number of bytes read
+    * @return  the actual number of bytes read, -1 is
+    *      returned when the end of the stream is reached.
+    * @exception IOException If an I/O error has occurred.
+    */
+    public int read(byte b[], int off, int len) throws IOException {
+        int m, bytesMoved;
+
+        if (len <= 0){
+            return 0;
+        }
+        /*
+        *Fast path: len bytes already available.
+        */
+
+        if (len <= stop - rdNext){
+            System.arraycopy(buff, rdNext, b, off, len);
+            rdNext += len;
+            return len;
+        }
+        /*
+        *General case: stream is closed or fill needs to be called
+        */
+        bytesMoved = 0;
+        for(;;){
+            if (rdNext != stop){
+                m = Math.min(len - bytesMoved, stop - rdNext);
+                System.arraycopy(buff, rdNext, b, off, m);
+                bytesMoved += m;
+                rdNext += m;
+                if (bytesMoved == len)
+                    return bytesMoved;
+                off += m;
+            }
+            if (isClosed){
+                return bytesMoved;
+            }
+            fill();
+
+        }
+    }
+    /**
+    * Reads into an array of bytes.  This method will
+    * block until some input is available.
+    * @param b the buffer into which the data is read
+    * @param off the start offset of the data
+    * @param len the maximum number of bytes read
+    * @return  the actual number of bytes read, -1 is
+    *      returned when the end of the stream is reached.
+    * @exception IOException If an I/O error has occurred.
+    */
+    public void  fill() throws IOException {
+        byte[] headerBuf = new byte[FCGIGlobalDefs.def_FCGIHeaderLen];
+        int headerLen = 0;
+        int status = 0;
+        int count = 0;
+        for(;;) {
+            /*
+            * If buffer is empty, do a read
+            */
+            if (rdNext == buffStop) {
+                try {
+                    count = in.read(buff, 0, buffLen);
+                } catch (IOException e) {
+                    setException(e);
+                    return;
+                }
+                if (count == 0) {
+                    setFCGIError(FCGIGlobalDefs.def_FCGIProtocolError);
+                    return;
+                }
+                rdNext = 0;
+                buffStop = count;       // 1 more than we read
+            }
+            /* Now buf is not empty: If the current record contains more content
+             * bytes, deliver all that are present in buff to callers buffer
+             * unless he asked for less than we have, in which case give him less
+             */
+            if (contentLen > 0) {
+                count = Math.min(contentLen, buffStop - rdNext);
+                contentLen -= count;
+                if (!skip) {
+                    stop = rdNext + count;
+                    return;
+                }
+                else {
+                    rdNext += count;
+                    if (contentLen > 0) {
+                        continue;
+                    }
+                    else {
+                        skip = false;
+                    }
+                }
+            }
+            /* Content has been consumed by client.
+             * If record was padded, skip over padding
+             */
+            if (paddingLen > 0) {
+                count = Math.min(paddingLen, buffStop - rdNext);
+                paddingLen -= count;
+                rdNext += count;
+                if (paddingLen > 0) {
+                    continue;    // more padding to read
+                }
+            }
+            /* All done with current record, including the padding.
+             * If we are in a recursive call from Process Header, deliver EOF
+             */
+            if (eorStop){
+                stop = rdNext;
+                isClosed = true;
+                return;
+            }
+            /*
+             * Fill header with bytes from input buffer - get the whole header.
+             */
+            count = Math.min(headerBuf.length - headerLen, buffStop - rdNext);
+            System.arraycopy(buff,rdNext, headerBuf, headerLen, count);
+            headerLen += count;
+            rdNext  += count;
+            if (headerLen < headerBuf.length) {
+                continue;
+            }
+            headerLen = 0;
+            /*
+             * Interperet the header. eorStop prevents ProcessHeader from
+             * reading past the end of record when using stream to read content
+             */
+            eorStop = true;
+            stop = rdNext;
+            status = 0;
+            status = new FCGIMessage(this).processHeader(headerBuf);
+            eorStop = false;
+            isClosed = false;
+            switch (status){
+            case FCGIGlobalDefs.def_FCGIStreamRecord:
+                if (contentLen == 0) {
+                    stop = rdNext;
+                    isClosed = true;
+                    return;
+                }
+                break;
+            case FCGIGlobalDefs.def_FCGISkip:
+                skip = true;
+                break;
+            case FCGIGlobalDefs.def_FCGIBeginRecord:
+                /*
+                * If this header marked the beginning of a new
+                * request, return role info to caller
+                */
+                return;
+            case FCGIGlobalDefs.def_FCGIMgmtRecord:
+                break;
+            default:
+                /*
+                * ASSERT
+                */
+                setFCGIError(status);
+                return;
+
+            }
+        }
+    }
+
+    /**
+     * Skips n bytes of input.
+     * @param n the number of bytes to be skipped
+     * @return  the actual number of bytes skipped.
+     * @exception IOException If an I/O error has occurred.
+     */
+    public long skip(long n) throws IOException {
+        byte data[] = new byte[(int)n];
+        return in.read(data);
+    }
+
+    /*
+     * An FCGI error has occurred. Save the error code in the stream
+     * for diagnostic purposes and set the stream state so that
+     * reads return EOF
+     */
+    public void setFCGIError(int errnum) {
+        /*
+        * Preserve only the first error.
+        */
+        if(errno == 0) {
+            errno = errnum;
+        }
+        isClosed = true;
+    }
+    /*
+    * An Exception has occurred. Save the Exception in the stream
+    * for diagnostic purposes and set the stream state so that
+    * reads return EOF
+    */
+    public void setException(Exception errexpt) {
+        /*
+        * Preserve only the first error.
+        */
+        if(errex == null) {
+            errex = errexpt;
+        }
+        isClosed = true;
+    }
+
+    /*
+    * Clear the stream error code and end-of-file indication.
+    */
+    public void clearFCGIError() {
+        errno = 0;
+        /*
+        * isClosed = false;
+        * XXX: should clear isClosed but work is needed to make it safe
+        * to do so.
+        */
+    }
+    /*
+    * Clear the stream error code and end-of-file indication.
+    */
+    public void clearException() {
+        errex = null;
+        /*
+        * isClosed = false;
+        * XXX: should clear isClosed but work is needed to make it safe
+        * to do so.
+        */
+    }
+
+    /*
+    * accessor method since var is private
+    */
+    public int getFCGIError() {
+        return errno;
+    }
+    /*
+    * accessor method since var is private
+    */
+    public Exception getException() {
+        return errex;
+    }
+    /*
+    * Re-initializes the stream to read data of the specified type.
+    */
+    public void setReaderType(int streamType) {
+
+        type = streamType;
+        eorStop = false;
+        skip = false;
+        contentLen = 0;
+        paddingLen = 0;
+        stop = rdNext;
+        isClosed = false;
+    }
+
+    /*
+    * Close the stream. This method does not really exist for BufferedInputStream in java,
+    * but is implemented here for compatibility with the FCGI structures being used. It
+    * doent really throw any IOExceptions either, but that's there for compatiblity with
+    * the InputStreamInterface.
+    */
+    public void close() throws IOException{
+        isClosed = true;
+        stop = rdNext;
+    }
+
+    /*
+    * Returns the number of bytes that can be read without blocking.
+    */
+
+    public int available() throws IOException {
+        return stop - rdNext + in.available();
+    }
+
+}
diff --git a/java/FCGIInterface.java b/java/FCGIInterface.java
new file mode 100644 (file)
index 0000000..ae88650
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * @(#)FCGIInterface.java
+ *
+ *
+ *      FastCGi compatibility package Interface
+ *
+ *
+ *  Copyright (c) 1996 Open Market, Inc.
+ *
+ * See the file "LICENSE.TERMS" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * $Id: FCGIInterface.java,v 1.1 1999/01/31 02:45:49 roberts Exp $
+ */
+
+import java.net.*;
+import java.io.*;
+import java.util.Properties;
+import FCGIGlobalDefs;
+import FCGIRequest;
+import FCGIInputStream;
+import FCGIOutputStream;
+import FCGIMessage;
+
+/*
+ * This is the FastCGI interface that the application calls to communicate with the
+ * FastCGI web server. This version is single threaded, and handles one request at
+ * a time, which is why we can have a static variable for it.
+ */
+public class FCGIInterface {
+
+    /*
+    * Class variables
+    */
+    public static FCGIRequest request = null;
+    public static boolean acceptCalled = false;
+    public static boolean isFCGI = true;
+    public static Properties startupProps;
+    public static ServerSocket srvSocket;
+
+    /*
+    * Accepts a new request from the HTTP server and creates
+    * a conventional execution environment for the request.
+    * If the application was invoked as a FastCGI server,
+    * the first call to FCGIaccept indicates that the application
+    * has completed its initialization and is ready to accept
+    * a request.  Subsequent calls to FCGI_accept indicate that
+    * the application has completed its processing of the
+    * current request and is ready to accept a new request.
+    * If the application was invoked as a CGI program, the first
+    * call to FCGIaccept is essentially a no-op and the second
+    * call returns EOF (-1) as does an error. Application should exit.
+    *
+    * If the application was invoked as a FastCGI server,
+    * and this is not the first call to this procedure,
+    * FCGIaccept first flushes any buffered output to the HTTP server.
+    *
+    * On every call, FCGIaccept accepts the new request and
+    * reads the FCGI_PARAMS stream into System.props. It also creates
+    * streams that understand FastCGI protocol and take input from
+    * the HTTP server send output and error output to the HTTP server,
+    * and assigns these new streams to System.in, System.out and
+    * System.err respectively.
+    *
+    * For now, we will just return an int to the caller, which is why
+    * this method catches, but doen't throw Exceptions.
+    *
+    */
+    public int FCGIaccept() {
+        int acceptResult = 0;
+
+        /*
+         * If first call, mark it and if fcgi save original system properties,
+         * If not first call, and  we are cgi, we should be gone.
+         */
+        if (!acceptCalled){
+            isFCGI = System.getProperties().containsKey("FCGI_PORT");
+            acceptCalled = true;
+            if (isFCGI) {
+                /*
+                 * save original system properties (nonrequest)
+                 * and get a server socket
+                 */
+                System.out.close();
+                System.err.close();
+                startupProps = new Properties(System.getProperties());
+                String str =
+                    new String(System.getProperty("FCGI_PORT"));
+                if (str.length() <= 0) {
+                    return -1;
+                }
+                int portNum = Integer.parseInt(str);
+
+                try {
+                    srvSocket = new ServerSocket(portNum);
+                } catch (IOException e) {
+                    request.socket = null;
+                    srvSocket = null;
+                    request = null;
+                    return -1;
+                }
+            }
+        }
+        else {
+            if (!isFCGI){
+                return -1;
+            }
+        }
+        /*
+         * If we are cgi, just leave everything as is, otherwise set up env
+         */
+        if (isFCGI){
+            try {
+                acceptResult = FCGIAccept();
+            } catch (IOException e) {
+                return -1;
+            }
+            if (acceptResult < 0){
+                return -1;
+            }
+
+            /*
+            * redirect stdin, stdout and stderr to fcgi socket
+            */
+            System.setIn(new BufferedInputStream(request.inStream, 8192));
+            System.setOut(new PrintStream(new BufferedOutputStream(
+                request.outStream, 8192)));
+            System.setErr(new PrintStream(new BufferedOutputStream(
+                request.errStream, 512)));
+            System.setProperties(request.params);
+        }
+        return 0;
+    }
+
+    /*
+     * Accepts a new request from the HTTP server.
+     * Finishes the request accepted by the previous call
+     * to FCGI_Accept. Sets up the FCGI environment and reads
+     * saved and per request environmental varaibles into
+     * the request object. (This is redundant on System.props
+     * as long as we can handle only one request object.)
+     */
+    int FCGIAccept() throws IOException{
+
+        boolean isNewConnection;
+        boolean errCloseEx = false;
+        boolean outCloseEx = false;
+
+        if (request != null) {
+            /*
+             * Complete the previous request
+             */
+            System.err.close();
+            System.out.close();
+            boolean prevRequestfailed = (errCloseEx || outCloseEx ||
+                request.inStream.getFCGIError() != 0 ||
+                request.inStream.getException() != null);
+            if (prevRequestfailed || !request.keepConnection ) {
+                request.socket.close();
+                request.socket = null;
+            }
+            if (prevRequestfailed) {
+                request = null;
+                return -1;
+            }
+        }
+        else    {
+            /*
+             * Get a Request and initialize some variables
+             */
+            request = new FCGIRequest();
+            request.socket = null;
+            request.inStream = null;
+        }
+        isNewConnection = false;
+
+        /*
+         * if connection isnt open accept a new connection (blocking)
+         */
+        for(;;) {
+            if (request.socket == null){
+                try {
+                    request.socket = srvSocket.accept();
+                } catch (IOException e) {
+                    request.socket = null;
+                    request = null;
+                    return -1;
+                }
+                isNewConnection = true;
+            }
+
+            /* Try reading from new connection. If the read fails and
+             * it was an old connection the web server probably closed it;
+             * try making a new connection before giving up
+             */
+            request.isBeginProcessed = false;
+            request.inStream =
+                new FCGIInputStream((FileInputStream)request.
+                socket.getInputStream(),
+                8192, 0, request);
+            request.inStream.fill();
+            if (request.isBeginProcessed) {
+                break;
+            }
+            request.socket.close();
+
+                request.socket = null;
+            if (isNewConnection) {
+                return -1;
+            }
+        }
+        /*
+        * Set up the objects for the new request
+        */
+        request.params = new Properties(startupProps);
+        switch(request.role) {
+        case FCGIGlobalDefs.def_FCGIResponder:
+            request.params.put("ROLE","RESPONDER");
+            break;
+        case FCGIGlobalDefs.def_FCGIAuthorizer:
+            request.params.put("ROLE", "AUTHORIZER");
+            break;
+        case FCGIGlobalDefs.def_FCGIFilter:
+            request.params.put("ROLE", "FILTER");
+            break;
+        default:
+            return -1;
+        }
+        request.inStream.setReaderType(FCGIGlobalDefs.def_FCGIParams);
+        /*
+         * read the rest of request parameters
+         */
+        if (new FCGIMessage(request.inStream).readParams(request.params) < 0) {
+            return -1;
+        }
+        request.inStream.setReaderType(FCGIGlobalDefs.def_FCGIStdin);
+        request.outStream
+            =  new FCGIOutputStream((FileOutputStream)request.socket.
+            getOutputStream(), 8192,
+            FCGIGlobalDefs.def_FCGIStdout,request);
+        request.errStream
+            = new FCGIOutputStream((FileOutputStream)request.socket.
+            getOutputStream(), 512,
+            FCGIGlobalDefs.def_FCGIStderr,request);
+        request.numWriters = 2;
+        return 0;
+    }
+}
diff --git a/java/FCGIMessage.java b/java/FCGIMessage.java
new file mode 100644 (file)
index 0000000..e0a710b
--- /dev/null
@@ -0,0 +1,403 @@
+/*
+ * @(#)FCGIMessage.java
+ *
+ *
+ *      FastCGi compatibility package Interface
+ *
+ *
+ *  Copyright (c) 1996 Open Market, Inc.
+ *
+ * See the file "LICENSE.TERMS" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * $Id: FCGIMessage.java,v 1.1 1999/01/31 02:45:52 roberts Exp $
+ */
+
+import java.io.*;
+import java.util.Properties;
+
+/* This class handles reading and building the fastcgi messages.
+ * For reading incoming mesages, we pass the input
+ * stream as a param to the constructor rather than to each method.
+ * Methods that build messages use and return internal buffers, so they
+ * dont need a stream.
+ */
+
+public class FCGIMessage {
+
+    /*
+     * Instance variables
+     */
+    /*
+     * FCGI Message Records
+     * The logical structures of the FCGI Message Records.
+     * Fields are originally 1 unsigned byte in message
+     * unless otherwise noted.
+     */
+    /*
+     * FCGI Header
+     */
+    private int  h_version;
+    private int  h_type;
+    private int  h_requestID;       // 2 bytes
+    private int  h_contentLength;   // 2 bytes
+    private int  h_paddingLength;
+    /*
+     * FCGI BeginRequest body.
+     */
+    private int  br_role;      // 2 bytes
+    private int  br_flags;
+
+    private FCGIInputStream in;
+
+    /*
+     * constructor - Java would do this implicitly.
+     */
+    public FCGIMessage(){
+        super();
+    }
+    /*
+     * constructor - get the stream.
+     */
+    public FCGIMessage(FCGIInputStream instream){
+        in = instream;
+    }
+
+    /*
+     * Message Reading Methods
+     */
+
+    /*
+     * Interpret the FCGI Message Header. Processes FCGI
+     * BeginRequest and Management messages. Param hdr is the header.
+     * The calling routine has to keep track of the stream reading
+     * management or use FCGIInputStream.fill() which does just that.
+     */
+    public int processHeader(byte[] hdr) throws IOException{
+        processHeaderBytes(hdr);
+        if (h_version != FCGIGlobalDefs.def_FCGIVersion1) {
+            return(FCGIGlobalDefs.def_FCGIUnsupportedVersion);
+        }
+        in.contentLen = h_contentLength;
+        in.paddingLen = h_paddingLength;
+        if (h_type == FCGIGlobalDefs.def_FCGIBeginRequest) {
+            return processBeginRecord(h_requestID);
+        }
+        if (h_requestID == FCGIGlobalDefs.def_FCGINullRequestID) {
+            return processManagementRecord(h_type);
+        }
+        if (h_requestID != in.request.requestID) {
+            return(FCGIGlobalDefs.def_FCGISkip);
+        }
+        if (h_type != in.type) {
+            return(FCGIGlobalDefs.def_FCGIProtocolError);
+        }
+        return(FCGIGlobalDefs.def_FCGIStreamRecord);
+    }
+
+    /* Put the unsigned bytes in the incoming FCGI header into
+     * integer form for Java, concatinating bytes when needed.
+     * Because Java has no unsigned byte type, we have to be careful
+     * about signed numeric promotion to int.
+     */
+    private void processHeaderBytes(byte[] hdrBuf){
+        h_version = hdrBuf[0] & 0xFF;
+        h_type = hdrBuf[1] & 0xFF;
+        h_requestID = ((hdrBuf[2] & 0xFF) << 8) | (hdrBuf[3] & 0xFF);
+        h_contentLength = ((hdrBuf[4] & 0xFF) << 8) | (hdrBuf[5] & 0xFF);
+        h_paddingLength = hdrBuf[6] & 0xFF;
+    }
+
+    /*
+     * Reads FCGI Begin Request Record.
+     */
+    public int processBeginRecord(int requestID) throws IOException {
+        byte beginReqBody[];
+        byte endReqMsg[];
+        if (requestID == 0 || in.contentLen
+            != FCGIGlobalDefs.def_FCGIEndReqBodyLen) {
+            return FCGIGlobalDefs.def_FCGIProtocolError;
+        }
+        /*
+         * If the webserver is multiplexing the connection,
+         * this library can't deal with it, so repond with
+         * FCGIEndReq message with protocolStatus FCGICantMpxConn
+         */
+        if (in.request.isBeginProcessed) {
+            endReqMsg = new byte[FCGIGlobalDefs.def_FCGIHeaderLen
+                + FCGIGlobalDefs.def_FCGIEndReqBodyLen];
+            System.arraycopy(makeHeader(
+                FCGIGlobalDefs.def_FCGIEndRequest,
+                requestID,
+                FCGIGlobalDefs.def_FCGIEndReqBodyLen,
+                0), 0,  endReqMsg, 0,
+                FCGIGlobalDefs.def_FCGIHeaderLen);
+            System.arraycopy(makeEndrequestBody(0,
+                FCGIGlobalDefs.def_FCGICantMpxConn), 0,
+                endReqMsg,
+                FCGIGlobalDefs.def_FCGIHeaderLen,
+                FCGIGlobalDefs.def_FCGIEndReqBodyLen);
+            /*
+             * since isBeginProcessed is first set below,this
+             * can't be out first call, so request.out is properly set
+             */
+            try {
+                in.request.outStream.write(endReqMsg, 0,
+                    FCGIGlobalDefs.def_FCGIHeaderLen
+                    + FCGIGlobalDefs.def_FCGIEndReqBodyLen);
+            } catch (IOException e){
+                in.request.outStream.setException(e);
+                return -1;
+            }
+        }
+        /*
+         * Accept this  new request. Read the record body
+         */
+        in.request.requestID = requestID;
+        beginReqBody =
+            new byte[FCGIGlobalDefs.def_FCGIBeginReqBodyLen];
+        if (in.read(beginReqBody, 0,
+            FCGIGlobalDefs.def_FCGIBeginReqBodyLen) !=
+            FCGIGlobalDefs.def_FCGIBeginReqBodyLen) {
+            return FCGIGlobalDefs.def_FCGIProtocolError;
+        }
+        br_flags = beginReqBody[2] & 0xFF;
+        in.request.keepConnection
+            = (br_flags & FCGIGlobalDefs.def_FCGIKeepConn) != 0;
+        br_role = ((beginReqBody[0] & 0xFF) << 8) | (beginReqBody[1] & 0xFF);
+        in.request.role = br_role;
+        in.request.isBeginProcessed = true;
+        return FCGIGlobalDefs.def_FCGIBeginRecord;
+    }
+
+    /*
+     * Reads and Responds to a Management Message. The only type of
+     * management message this library understands is FCGIGetValues.
+     * The only variables that this library's FCGIGetValues understands
+     * are def_FCGIMaxConns, def_FCGIMaxReqs, and def_FCGIMpxsConns.
+     * Ignore the other management variables, and repsond to other
+     * management messages with FCGIUnknownType.
+     */
+    public int processManagementRecord(int type) throws IOException {
+
+        byte[] response = new byte[64];
+        int wrndx = response[FCGIGlobalDefs.def_FCGIHeaderLen];
+        int value, len, plen;
+        if (type == FCGIGlobalDefs.def_FCGIGetValues) {
+            Properties tmpProps = new Properties();
+            readParams(tmpProps);
+
+            if (in.getFCGIError() != 0 || in.contentLen != 0) {
+                return FCGIGlobalDefs.def_FCGIProtocolError;
+            }
+            if (tmpProps.containsKey(
+                FCGIGlobalDefs.def_FCGIMaxConns)) {
+                makeNameVal(
+                    FCGIGlobalDefs.def_FCGIMaxConns, "1",
+                    response, wrndx);
+            }
+            else {
+                if (tmpProps.containsKey(
+                    FCGIGlobalDefs.def_FCGIMaxReqs)) {
+                    makeNameVal(
+                        FCGIGlobalDefs.def_FCGIMaxReqs, "1",
+                        response, wrndx);
+                }
+                else {
+                    if (tmpProps.containsKey(
+                        FCGIGlobalDefs.def_FCGIMaxConns)) {
+                        makeNameVal(
+                            FCGIGlobalDefs.def_FCGIMpxsConns, "0",
+                            response, wrndx);
+                    }
+                }
+            }
+            plen = 64 - wrndx;
+            len = wrndx - FCGIGlobalDefs.def_FCGIHeaderLen;
+            System.arraycopy(makeHeader(
+                FCGIGlobalDefs.def_FCGIGetValuesResult,
+                FCGIGlobalDefs.def_FCGINullRequestID,
+                len, plen), 0,
+                response, 0,
+                FCGIGlobalDefs.def_FCGIHeaderLen);
+        }
+        else {
+            plen = len =
+                FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen;
+            System.arraycopy(makeHeader(
+                FCGIGlobalDefs.def_FCGIUnknownType,
+                FCGIGlobalDefs.def_FCGINullRequestID,
+                len, 0), 0,
+                response, 0,
+                FCGIGlobalDefs.def_FCGIHeaderLen);
+            System.arraycopy(makeUnknownTypeBodyBody(h_type), 0,
+                response,
+                FCGIGlobalDefs.def_FCGIHeaderLen,
+                FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen);
+        }
+        /*
+         * No guarantee that we have a request yet, so
+         * dont use fcgi output stream to reference socket, instead
+         * use the FileInputStream that refrences it. Also
+         * nowhere to save exception, since this is not FCGI stream.
+         */
+
+        try {
+            in.request.socket.getOutputStream().write(response, 0,
+                FCGIGlobalDefs.def_FCGIHeaderLen +
+                FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen);
+
+        } catch (IOException e){
+            return -1;
+        }
+        return FCGIGlobalDefs.def_FCGIMgmtRecord;
+    }
+
+    /*
+     * Makes a name/value with name = string of some length, and
+     * value a 1 byte integer. Pretty specific to what we are doing
+     * above.
+     */
+    void makeNameVal(String name, String value, byte[] dest, int pos) {
+        int nameLen = name.length();
+        if (nameLen < 0x80) {
+            dest[pos++] = (byte)nameLen;
+        }else {
+            dest[pos++] = (byte)(((nameLen >> 24) | 0x80) & 0xff);
+            dest[pos++] = (byte)((nameLen >> 16) & 0xff);
+            dest[pos++] = (byte)((nameLen >> 8) & 0xff);
+            dest[pos++] = (byte)nameLen;
+        }
+        int valLen = value.length();
+        if (valLen < 0x80) {
+            dest[pos++] =  (byte)valLen;
+        }else {
+            dest[pos++] = (byte)(((valLen >> 24) | 0x80) & 0xff);
+            dest[pos++] = (byte)((valLen >> 16) & 0xff);
+            dest[pos++] = (byte)((valLen >> 8) & 0xff);
+            dest[pos++] = (byte)valLen;
+        }
+        name.getBytes(0, nameLen, dest, pos);
+        pos += nameLen;
+        value.getBytes(0, valLen, dest, pos);
+        pos += valLen;
+    }
+
+    /*
+     * Read FCGI name-value pairs from a stream until EOF. Put them
+     * into a Properties object, storing both as strings.
+     */
+    public int readParams(Properties props) throws IOException{
+        int nameLen, valueLen;
+        byte lenBuff[] = new byte[3];
+        int i = 1;
+
+        while ((nameLen = in.read()) != -1) {
+            i++;
+            if ((nameLen & 0x80) != 0) {
+                if ((in.read( lenBuff, 0, 3)) != 3) {
+                    in.setFCGIError(
+                        FCGIGlobalDefs.def_FCGIParamsError);
+                    return -1;
+                }
+                nameLen = ((nameLen & 0x7f) << 24)
+                    | ((lenBuff[0] & 0xFF) << 16)
+                    | ((lenBuff[1] & 0xFF) << 8)
+                    | (lenBuff[2] & 0xFF);
+            }
+
+            if ((valueLen = in.read()) == -1) {
+                in.setFCGIError(
+                    FCGIGlobalDefs.def_FCGIParamsError);
+                return -1;
+            }
+            if ((valueLen & 0x80) != 0) {
+                if ((in.read( lenBuff, 0, 3)) != 3) {
+                    in.setFCGIError(
+                        FCGIGlobalDefs.def_FCGIParamsError);
+                    return -1;
+                }
+                valueLen = ((valueLen & 0x7f) << 24)
+                    | ((lenBuff[0] & 0xFF) << 16)
+                    | ((lenBuff[1] & 0xFF) << 8)
+                    | (lenBuff[2] & 0xFF);
+            }
+
+            /*
+             * nameLen and valueLen are now valid; read the name
+             * and the value from the stream and construct a standard
+             * environmental entity
+             */
+            byte[] name  = new byte[nameLen];
+            byte[] value = new byte[valueLen];
+            if (in.read(name ,0,  nameLen) != nameLen) {
+                in.setFCGIError(
+                    FCGIGlobalDefs.def_FCGIParamsError);
+                return -1;
+            }
+
+            if(in.read(value, 0, valueLen) != valueLen) {
+                in.setFCGIError(
+                    FCGIGlobalDefs.def_FCGIParamsError);
+                return -1;
+            }
+            String strName  = new String(name, 0, 0, name.length);
+            String strValue = new String(value, 0, 0, value.length);
+            props.put(strName, strValue);
+        }
+        return 0;
+
+
+    }
+    /*
+     * Message Building Methods
+     */
+
+    /*
+     * Build an FCGI Message Header -
+     */
+    public byte[] makeHeader(int type,
+        int requestId,
+        int contentLength,
+        int paddingLength) {
+        byte[] header = new byte[FCGIGlobalDefs.def_FCGIHeaderLen];
+        header[0]   = (byte)FCGIGlobalDefs.def_FCGIVersion1;
+        header[1]   = (byte)type;
+        header[2]   = (byte)((requestId      >> 8) & 0xff);
+        header[3]   = (byte)((requestId          ) & 0xff);
+        header[4]   = (byte)((contentLength  >> 8) & 0xff);
+        header[5]   = (byte)((contentLength      ) & 0xff);
+        header[6]   = (byte)paddingLength;
+        header[7]   =  0;  //reserved byte
+        return header;
+    }
+    /*
+     * Build an FCGI Message End Request Body
+     */
+    public byte[] makeEndrequestBody(int appStatus,int protocolStatus){
+        byte body[] = new byte[FCGIGlobalDefs.def_FCGIEndReqBodyLen];
+        body[0] = (byte)((appStatus >> 24) & 0xff);
+        body[1] = (byte)((appStatus >> 16) & 0xff);
+        body[2] = (byte)((appStatus >>  8) & 0xff);
+        body[3] = (byte)((appStatus      ) & 0xff);
+        body[4] = (byte)protocolStatus;
+        for (int i = 5; i < 8; i++) {
+            body[i] = 0;
+        }
+        return body;
+    }
+    /*
+     * Build an FCGI Message UnknownTypeBodyBody
+     */
+    public byte[] makeUnknownTypeBodyBody(int type){
+        byte body[] =
+            new byte[FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen];
+        body[0] = (byte)type;
+        for (int i = 1;
+            i < FCGIGlobalDefs.def_FCGIUnknownBodyTypeBodyLen; i++) {
+            body[i] = 0;
+        }
+        return body;
+    }
+
+} //end class
diff --git a/java/FCGIOutputStream.java b/java/FCGIOutputStream.java
new file mode 100644 (file)
index 0000000..32fdd40
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * @(#)FCGIOutputStream.java
+ *
+ *      FastCGi compatibility package Interface
+ *
+ *  Copyright (c) 1996 Open Market, Inc.
+ *
+ * See the file "LICENSE.TERMS" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * $Id: FCGIOutputStream.java,v 1.1 1999/01/31 02:45:53 roberts Exp $
+ */
+
+import java.io.*;
+import  FCGIRequest;
+import  FCGIGlobalDefs;
+import  FCGIMessage;
+import  FCGIInputStream;
+
+/**
+ * This stream understands FCGI prototcol.
+ */
+
+public class FCGIOutputStream extends OutputStream {
+
+    /* Stream vars */
+
+    public int wrNext;
+    public int stop;
+    public boolean isClosed;
+
+    /* require methods to set, get and clear */
+    private int errno;
+    private Exception errex;
+
+    /* data vars */
+
+    public byte buff[];
+    public int buffLen;
+    public int buffStop;
+    public int type;
+    public boolean isAnythingWritten;
+    public boolean rawWrite;
+    public FCGIRequest request;
+
+    public FileOutputStream out;
+
+    /**
+    * Creates a new output stream to manage fcgi prototcol stuff
+    * @param out the output stream  buflen  length of buffer streamType
+    */
+    public FCGIOutputStream(FileOutputStream outStream,
+        int bufLen, int streamType,
+        FCGIRequest inreq) {
+        out = outStream;
+        buffLen = Math.min(bufLen, FCGIGlobalDefs.def_FCGIMaxLen);
+        buff = new byte[buffLen];
+        type = streamType;
+        stop = buffStop = buffLen;
+        isAnythingWritten = false;
+        rawWrite = false;
+        wrNext = FCGIGlobalDefs.def_FCGIHeaderLen;
+        isClosed = false;
+        request = inreq;
+    }
+
+    /**
+    * Writes a byte to the output stream.
+    */
+    public void  write(int c) throws IOException {
+        if(wrNext != stop) {
+            buff[wrNext++] = (byte)c;
+            return;
+        }
+        if(isClosed) {
+            throw new EOFException();
+        }
+        empty(false);
+        if(wrNext != stop) {
+            buff[wrNext++] = (byte)c;
+            return;
+        }
+        /* NOTE: ASSERT(stream->isClosed); */
+        /* bug in emptyBuffProc if not */
+        throw new EOFException();
+    }
+
+    /**
+    * Writes an array of bytes. This method will block until the bytes
+    * are actually written.
+    * @param b  the data to be written
+    */
+    public  void write(byte b[]) throws IOException{
+        write(b, 0, b.length);
+    }
+
+    /**
+    * Writes len consecutive bytes from off in the array b
+    * into the output stream.  Performs no interpretation
+    * of the output bytes. Making the user convert the string to
+    * bytes is in line with current Java practice.
+    */
+    public void write(byte b[], int off, int len) throws IOException {
+        int m, bytesMoved;
+        /*
+        * Fast path: room for n bytes in the buffer
+        */
+        if(len <= (stop - wrNext)) {
+            System.arraycopy(b, off, buff, wrNext, len);
+            wrNext += len;
+            return;
+        }
+        /*
+        * General case: stream is closed or buffer empty procedure
+        * needs to be called
+        */
+        bytesMoved = 0;
+        for (;;) {
+            if(wrNext != stop) {
+                m = Math.min(len - bytesMoved, stop - wrNext);
+                System.arraycopy(b, off, buff, wrNext, m);
+                bytesMoved += m;
+                wrNext += m;
+                if(bytesMoved == len) {
+                    return;
+                }
+                off += m;
+            }
+            if(isClosed) {
+                throw new EOFException();
+            }
+            empty(false);
+        }
+    }
+
+    /**
+    * Encapsulates any buffered stream content in a FastCGI
+    * record.  If !doClose, writes the data, making the buffer
+    * empty.
+    */
+    public void empty(boolean doClose) throws IOException {
+        int cLen;
+        /*
+        * Alignment padding omitted in Java
+        */
+        if (!rawWrite) {
+            cLen = wrNext - FCGIGlobalDefs.def_FCGIHeaderLen;
+            if(cLen > 0) {
+                System.arraycopy(new FCGIMessage().makeHeader(type,
+                    request.requestID, cLen, 0),
+                    0, buff, 0,
+                    FCGIGlobalDefs.def_FCGIHeaderLen);
+            }
+            else {
+                wrNext = 0;
+            }
+        }
+        if (doClose) {
+            writeCloseRecords();
+        }
+        if (wrNext != 0) {
+            isAnythingWritten = true;
+            try {
+                out.write(buff, 0, wrNext);
+            } catch (IOException e) {
+                setException(e);
+                return;
+            }
+            wrNext = 0;
+        }
+        /*
+        * The buffer is empty.
+        */
+        if(!rawWrite) {
+            wrNext += FCGIGlobalDefs.def_FCGIHeaderLen;
+        }
+    }
+
+    /**
+    * Close the stream.
+    */
+    public void  close() throws IOException {
+        if (isClosed) {
+            return;
+        }
+        empty(true);
+        /*
+        * if isClosed, will return with EOFException from write.
+        */
+        isClosed = true;
+        stop = wrNext;
+        return;
+    }
+
+    /**
+    * Flushes any buffered output.
+    * Server-push is a legitimate application of flush.
+    * Otherwise, it is not very useful, since FCGIAccept
+    * does it implicitly.  flush may reduce performance
+    * by increasing the total number of operating system calls
+    * the application makes.
+    */
+    public void flush() throws IOException {
+        if (isClosed) {
+            return;
+        }
+        empty(false);
+        /*
+        * if isClosed, will return with EOFException from write.
+        */
+        return;
+    }
+
+    /**
+    * An FCGI error has occurred. Save the error code in the stream
+    * for diagnostic purposes and set the stream state so that
+    * reads return EOF
+    */
+    public void setFCGIError(int errnum) {
+        /*
+        * Preserve only the first error.
+        */
+        if (errno == 0) {
+            errno = errnum;
+        }
+        isClosed = true;
+    }
+
+    /**
+    * An Exception has occurred. Save the Exception in the stream
+    * for diagnostic purposes and set the stream state so that
+    * reads return EOF
+    */
+    public void setException(Exception errexpt) {
+        /*
+        * Preserve only the first error.
+        */
+        if (errex == null) {
+            errex = errexpt;
+        }
+        isClosed = true;
+    }
+
+    /**
+    * Clear the stream error code and end-of-file indication.
+    */
+    public void clearFCGIError() {
+        errno = 0;
+        /*
+        * isClosed = false;
+        * XXX: should clear isClosed but work is needed to make it safe
+        * to do so.
+        */
+    }
+
+    /**
+    * Clear the stream error code and end-of-file indication.
+    */
+    public void clearException() {
+        errex = null;
+        /*
+        * isClosed = false;
+        * XXX: should clear isClosed but work is needed to make it safe
+        * to do so.
+        */
+    }
+
+    /**
+    * accessor method since var is private
+    */
+    public int etFCGIError() {
+        return errno;
+    }
+
+    /**
+    * accessor method since var is private
+    */
+    public Exception getException() {
+        return errex;
+    }
+
+    /**
+    * Writes an EOF record for the stream content if necessary.
+    * If this is the last writer to close, writes an FCGI_END_REQUEST
+    * record.
+    */
+    public void writeCloseRecords() throws IOException {
+        FCGIMessage msg = new FCGIMessage();
+        /*
+        * Enter rawWrite mode so final records won't be
+        * encapsulated as
+        * stream data.
+        */
+        rawWrite = true;
+        /*
+        * Generate EOF for stream content if needed.
+        */
+        if(!(type == FCGIGlobalDefs.def_FCGIStderr
+            && wrNext == 0
+            && !isAnythingWritten)) {
+            byte hdr[] =
+                new byte[FCGIGlobalDefs.def_FCGIHeaderLen];
+            System.arraycopy(msg.makeHeader(type,
+                request.requestID,
+                0, 0),
+                0, hdr,0,
+                FCGIGlobalDefs.def_FCGIHeaderLen);
+            write(hdr, 0, hdr.length);
+        }
+        /*
+        * Generate FCGI_END_REQUEST record if needed.
+        */
+        if(request.numWriters == 1) {
+            byte endReq[] =
+                new byte[FCGIGlobalDefs.def_FCGIHeaderLen
+                + FCGIGlobalDefs.def_FCGIEndReqBodyLen];
+            System.arraycopy(msg.makeHeader(
+                FCGIGlobalDefs.def_FCGIEndRequest,
+                request.requestID,
+                FCGIGlobalDefs.def_FCGIEndReqBodyLen,0),
+                0, endReq, 0,
+                FCGIGlobalDefs.def_FCGIHeaderLen);
+            System.arraycopy(msg.makeEndrequestBody(
+                request.appStatus,
+                FCGIGlobalDefs.def_FCGIRequestComplete),
+                0,endReq,
+                FCGIGlobalDefs.def_FCGIHeaderLen,
+                FCGIGlobalDefs.def_FCGIEndReqBodyLen);
+            write(endReq,0, FCGIGlobalDefs.def_FCGIHeaderLen
+                + FCGIGlobalDefs.def_FCGIEndReqBodyLen);
+        }
+        request.numWriters--;
+    }
+}
+
diff --git a/java/FCGIRequest.java b/java/FCGIRequest.java
new file mode 100644 (file)
index 0000000..d1d44b9
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * @(#)FCGIRequest.java
+ *
+ *  FastCGi compatibility package Interface
+ *
+ *  Copyright (c) 1996 Open Market, Inc.
+ *
+ * See the file "LICENSE.TERMS" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * $Id: FCGIRequest.java,v 1.1 1999/01/31 02:45:55 roberts Exp $
+ */
+
+import java.net.*;
+import java.io.FileDescriptor;
+import java.util.Properties;
+import FCGIInputStream;
+import FCGIOutputStream;
+public class FCGIRequest {
+
+    /* This class has no methods. Right now we are single threaded
+    * so there is only one request object at any given time which
+    * is refrenced by an FCGIInterface class variable . All of this
+    * object's data could just as easily be declared directly there.
+    * When we thread, this will change, so we might as well use a
+    * seperate class. In line with this thinking, though somewhat
+    * more perversely, we kept the socket here.
+    */
+    /*
+     * class variables
+     */
+    /*public static Socket  socket; */
+    // same for all requests
+
+    /*
+     * instance variables
+     */
+    public Socket       socket;
+    public boolean      isBeginProcessed;
+    public int      requestID;
+    public boolean      keepConnection;
+    public int      role;
+    public int      appStatus;
+    public int      numWriters;
+    public FCGIInputStream  inStream;
+    public FCGIOutputStream outStream;
+    public FCGIOutputStream errStream;
+    public Properties   params;
+}
+
+