// CGIplus.java // Compile using: $ javac "CGIplus.java" // // The Java may be a bit brain-dead ... still very much the novice! // This class can be expected to undergo refinement as expertise grows. // Developed using the first-release JDK1.1 beta kit for OpenVMS Alpha. import java.io.*; import java.util.Vector; /** * This class allows CGI scripting to be supported relatively simply using * Java. Relies on the WASD HTTPd CGIplus environment providing the CGI * variables in a data stream rather than as process environment variables. *

* Hence scripts must be activated via mapping rules that execute them within * the CGIplus environment, although the scripts themselves do not have to * "persist" in the usual CGIplus script manner, just "System.exit(0)" when * finished processing (at the slight cost of destroying the subprocess). *

* Currently supports GET and "x-www-form-urlencoded" POST HTTP method scripts. * Will be expanded to more fully support POST processing as time permits. * * @version 1.0.0, 09-DEC-1997 * @author MGD */ public class CGIplus { private static int usageCount = 0, cgiVarCount = 0, formFieldCount = 0, nextCgiVarCount = 0, nextFormFieldCount = 0; private static FileReader bodyf = null; private static BufferedReader cgipi = null, bodys = null; private static Vector cgiVariables = null, urlEncodedForm = null; private static String cgiPlusEof = null, requestBody = null, line = null; ////////////////////////////////////////////////////////////////////////////// /** * Begin script processing, and synchronize persistant (CGIplus) scripts. * For the first call checks for the CGIPLUSEOF string (if not available * reports it as an error and exits), then opens the CGIPLUSIN stream. * Wait for a request to become available (first record read and discarded). * Then read series of records up until the first empty record (indicates end * of CGI variables), placing each of these records into a vector object for * later search and retrieval. * * @param none * @return none * @exception none */ public void begin () { if (cgiPlusEof == null) { // get the end-of-file (script output) marker cgiPlusEof = System.getProperty("cgipluseof"); if (cgiPlusEof == null) { System.out.println("Sorry ... from a CGIplus environment only!"); System.exit(0); } try { cgipi = new BufferedReader(new FileReader("CGIPLUSIN:")); } catch (Exception e) { e.printStackTrace(); System.exit(0); } } try { // synchronize on the first line and discard line = cgipi.readLine(); // get rest of CGI variables cgiVariables = new Vector(32); cgiVarCount = 0; while ((line = cgipi.readLine()) != null) { if (line.equals("")) break; cgiVariables.addElement(line); cgiVarCount++; } } catch (Exception e) { e.printStackTrace(); System.exit(0); } usageCount++; } ////////////////////////////////////////////////////////////////////////////// /** * Conclude the response. * Write the CGIplus end-of-output value to the server. * Release any resources not relevant to next request (if CGIplus). * * @param none * @return none * @exception none */ public void end () { if (cgiPlusEof == null) begin(); System.out.print(cgiPlusEof); // allow the major resources to be GCed while quiescent (if CGIplus) cgiVariables = null; urlEncodedForm = null; // close input streams if (bodys != null) { try { bodys.close(); } catch (Exception e) { e.printStackTrace(); System.exit(0); } bodys = null; } if (bodyf != null) { try { bodyf.close(); } catch (Exception e) { e.printStackTrace(); System.exit(0); } bodyf = null; } } ////////////////////////////////////////////////////////////////////////////// /** * Return the number of times the script has been used. * Relevant only when behaving as a persistant, CGIplus script. * * @param none * @return none * @exception none */ public int getUsageCount () { return usageCount; } ////////////////////////////////////////////////////////////////////////////// /** * Return the value of the specified CGI variable name. * It is less expensive but not mandatory to supply the the "WWW_" prefix. * Returns null if the specified variable name does not exist. * * @param varName the name of the CGI variable * @return the CGI variable string * @exception none */ public String getCgiVar (String varName) { int cnt; if (cgiPlusEof == null) begin(); if (!varName.startsWith("WWW_")) { varName = "WWW_" + varName; } for (cnt = 0; cnt < cgiVariables.size(); cnt++) { line = (String)cgiVariables.elementAt(cnt); if (!line.startsWith(varName+"=")) continue; return line.substring(varName.length()+1); } return null; } ////////////////////////////////////////////////////////////////////////////// /** * Successive calls return each CGI variable 'name=value' pair. * Returns null and resets when the CGI variables are exhausted. * * @param none * @return the 'name=value' string * @exception none */ public String nextCgiVar () { if (cgiPlusEof == null) begin(); if (nextCgiVarCount >= cgiVariables.size()) { nextCgiVarCount = 0; return null; } return (String)cgiVariables.elementAt(nextCgiVarCount++); } ////////////////////////////////////////////////////////////////////////////// /** * Return the number of CGI variables available. * * @param none * @return count of CGI variables * @exception none */ public int getCgiVarCount () { if (cgiPlusEof == null) begin(); return cgiVarCount; } ////////////////////////////////////////////////////////////////////////////// /** * Output all CGI variable 'name=value' pairs (for debugging, etc.) * * @param none * @return none * @exception none */ public void dumpCgiVar () { int cnt; if (cgiPlusEof == null) begin(); for (cnt = 0; cnt < cgiVariables.size(); cnt++) { System.out.print((String)cgiVariables.elementAt(cnt)); } } ////////////////////////////////////////////////////////////////////////////// /** * Returns the MIME content-type of the body of a POSTed request. * * @param none * @return body content-type * @exception none */ public String getContentType () { return getCgiVar("WWW_CONTENT_TYPE"); } ////////////////////////////////////////////////////////////////////////////// /** * Tests whether the request is POSTed and "www-form-urlencoded". * * @param none * @return true or false * @exception none */ public boolean isPOSTedForm () { String type, method; method = getCgiVar("WWW_REQUEST_METHOD"); if (!method.equals("POST")) return false; type = getCgiVar("WWW_CONTENT_TYPE"); return type.equals("application/x-www-form-urlencoded"); } ////////////////////////////////////////////////////////////////////////////// /** * Returns the content-length of the body for a POSTed request. * * @param none * @return length of POSTed body * @exception none */ public int getContentLength () { return Integer.parseInt(getCgiVar("WWW_CONTENT_LENGTH")); } ////////////////////////////////////////////////////////////////////////////// /** * Read a line from the body of the request (for POSTed requests). * * @param none * @return the line (or null if body exhausted) * @exception none */ public String readBodyLine () { if (bodys == null) { try { bodys = new BufferedReader(bodyf = new FileReader("HTTP$INPUT:")); } catch (Exception e) { e.printStackTrace(); System.exit(0); } } try { return bodys.readLine(); } catch (Exception e) { e.printStackTrace(); System.exit(0); } return null; } ////////////////////////////////////////////////////////////////////////////// /** * Output all lines in request body (for debugging POSTed requests, etc.) * * @param none * @return none * @exception none */ public void dumpBody () { if (cgiPlusEof == null) begin(); while ((line = readBodyLine()) != null) System.out.print(line); } ////////////////////////////////////////////////////////////////////////////// /** * Read entire request body into 'field=value' pairs. * For a 'x-www-form-urlencoded', POSTed request. * * @param none * @return none * @exception none */ // forms tend to be on the small side, read entire body into one string! private void readUrlEncodedForm () { int idx1, idx2; if (cgiPlusEof == null) begin(); line = getContentType(); if (!line.equals("application/x-www-form-urlencoded")) { System.out.println("Not an \"application/x-www-form-urlencoded\" request."); System.exit(0); } requestBody = ""; while ((line = readBodyLine()) != null) { requestBody += line; } urlEncodedForm = new Vector(32); for (idx1 = idx2 = 0;;) { idx2 = requestBody.indexOf('&',idx1); if (idx2 >= 0) { line = urlDecode(requestBody.substring(idx1,idx2)); urlEncodedForm.addElement(line); idx1 = idx2 + 1; formFieldCount++; } else { if (idx1 < requestBody.length()) { line = urlDecode(requestBody.substring(idx1)); urlEncodedForm.addElement(line); formFieldCount++; } break; } } } ////////////////////////////////////////////////////////////////////////////// /** * Output all form 'field=value' pairs (for debugging, etc). * For a 'x-www-form-urlencoded', POSTed request. * * @param none * @return the 'name=value' string * @exception none */ public void dumpForm () { int cnt; if (urlEncodedForm == null) readUrlEncodedForm(); if (urlEncodedForm == null) return; for (cnt = 0; cnt < urlEncodedForm.size(); cnt++) { System.out.print((String)urlEncodedForm.elementAt(cnt)); } } ////////////////////////////////////////////////////////////////////////////// /** * Return the value of the specified form field name. * Returns null if the specified field name does not exist. * For a 'x-www-form-urlencoded', POSTed request. * * @param fieldName the name of the field * @return the (URL-decoded) field value * @exception none */ public String getFormField (String fieldName) { int cnt; if (cgiPlusEof == null) begin(); if (urlEncodedForm == null) readUrlEncodedForm(); for (cnt = 0; cnt < urlEncodedForm.size(); cnt++) { line = (String)urlEncodedForm.elementAt(cnt); if (!line.startsWith(fieldName+"=")) continue; return line.substring(fieldName.length()+1); } return null; } ////////////////////////////////////////////////////////////////////////////// /** * Successive calls return each form field 'name=value' pair. * Returns null and resets when the form fields are exhausted. * For a 'x-www-form-urlencoded', POSTed request. * * @param none * @return the 'name=value' string * @exception none */ public String nextFormField () { if (cgiPlusEof == null) begin(); if (urlEncodedForm == null) readUrlEncodedForm(); if (nextFormFieldCount >= urlEncodedForm.size()) { nextFormFieldCount = 0; return null; } return (String)urlEncodedForm.elementAt(nextFormFieldCount++); } ////////////////////////////////////////////////////////////////////////////// /** * Return the number of form fields. * For a 'x-www-form-urlencoded', POSTed request. * * @param none * @return count of fields in request * @exception none */ public int getFormFieldCount () { if (cgiPlusEof == null) begin(); if (urlEncodedForm == null) readUrlEncodedForm(); if (urlEncodedForm == null) return 0; return formFieldCount; } ////////////////////////////////////////////////////////////////////////////// /** * Decode the supplied URL-encoded string. * Converts '+' into ' ' and "%nn" hex-encoded values into their ASCII * characters. * * @param estr the url-encoded string * @return the decoded string * @exception none */ public String urlDecode (String estr) { int ridx, eidx; char ch; char[] dstr; if (estr == null) return null; dstr = new char[estr.length()]; ridx = 0; for (eidx = 0; eidx < estr.length(); eidx++) { ch = estr.charAt(eidx); if (ch == '+') { dstr[ridx++] = ' '; } else if (ch == '%') { try { dstr[ridx++] = (char) Integer.parseInt(estr.substring(eidx+1,eidx+3), 16); eidx += 2; } catch (NumberFormatException e) { System.out.println(estr.substring(eidx+1,eidx+3) + " is an invalid hexadecimal code"); e.printStackTrace(); System.exit(0); } } else { dstr[ridx++] = ch; } } return String.valueOf(dstr,0,ridx); } ////////////////////////////////////////////////////////////////////////////// }