- From: Yves Lafon <ylafon@w3.org>
- Date: Tue, 10 Mar 1998 17:58:34 +0100 (MET)
- To: Malcolm McIlhagga <malcolm@cogs.susx.ac.uk>
- cc: www-jigsaw@w3.org
- Message-ID: <Pine.GSO.3.96.980310175524.21137A-200000@tarantula.inria.fr>
On Tue, 10 Mar 1998, Malcolm McIlhagga wrote: > I have recently installed Jigsaw 2 on my sun machine (Solaris 2.6, > jdk1.1.5) > looking at > http://lists.w3.org/Archives/Public/www-jigsaw/1998MarApr/0002.html it > tells me that I should have CGIFrame for 2.0alpha, but resource class > org.w3c.jigsaw.frames.CGIFrame is not in the packages under the Jigsaw > installation. where > can I get this class from and is that all I need to make my perl script > work? This frame was not yet ported when the first release (alpha1) went out. We plan to have a new release soon, as almost everything is now ported to 2.0 (the latest is the Proxy, yesterday :) ). The source code of CgiFrame (org.w3c.jigsaw.frames.CgiFrame) is attached. Regards, /\ - Yves Lafon - World Wide Web Consortium - /\ / \ Architecture Domain - Jigsaw / \ \/\ / \ / \ http://www.w3.org/People/Lafon - ylafon@w3.org
// CgiFrame.java // $Id: CgiFrame.java,v 1.2 1998/02/13 10:20:43 bmahe Exp $ // (c) COPYRIGHT MIT and INRIA, 1996. // Please first read the full copyright statement in file COPYRIGHT.html package org.w3c.jigsaw.frames ; import java.io.* ; import java.util.*; import java.net.*; import org.w3c.tools.resources.*; import org.w3c.tools.resources.ProtocolException; import org.w3c.www.mime.* ; import org.w3c.jigsaw.http.* ; import org.w3c.www.http.*; import org.w3c.jigsaw.auth.AuthFilter; /** * Parsing the CGI output - The CGIHeaderHolder, to hold CGI headers. */ class CGIHeaderHolder implements MimeHeaderHolder { // Status and Location deserve special treatments: String status = null; String location = null; // Anyway, he is going to pay for using CGI Hashtable headers = null; // The MIME parse we are attached to: MimeParser parser = null; /** * The parsing is now about to start, take any appropriate action. * This hook can return a <strong>true</strong> boolean value to enforce * the MIME parser into transparent mode (eg the parser will <em>not</em> * try to parse any headers. * <p>This hack is primarily defined for HTTP/0.9 support, it might * also be usefull for other hacks. * @param parser The Mime parser. * @return A boolean <strong>true</strong> if the MimeParser shouldn't * continue the parsing, <strong>false</strong> otherwise. */ public boolean notifyBeginParsing(MimeParser parser) throws IOException { return false; } /** * All the headers have been parsed, take any appropriate actions. * @param parser The Mime parser. */ public void notifyEndParsing(MimeParser parser) throws IOException { return ; } /** * A new header has been emited by the script. * If the script is not an NPH, then it <strong>must</strong> at least * emit one header, so we are safe here, although people may not be safe * against the spec. * @param name The header name. * @param buf The header bytes. * @param off The begining of the value bytes in above buffer. * @param len The length of the value bytes in above buffer. */ public void notifyHeader(String name, byte buf[], int off, int len) throws MimeParserException { if ( name.equalsIgnoreCase("status") ) { status = new String(buf, 0, off, len); } else if ( name.equalsIgnoreCase("location") ) { location = new String(buf, 0, off, len); } else { String extraval = new String(buf, 0, off, len); if ( headers == null ) { headers = new Hashtable(11); } else { String val = (String) headers.get(name.toLowerCase()); if ( val != null ) extraval = val + "," + extraval; } headers.put(name.toLowerCase(), extraval); } } /** * Get the status emited by the script. */ public String getStatus() { return status; } /** * Get the location header value emited by the script. */ public String getLocation() { return location; } /** * Get any header value (except status and location). * @param name The name of the header to fetch. * @return The string value of requested header, or <strong>null</strong> * if header was not defined. */ public String getValue(String name) { return (headers == null) ? null : (String) headers.get(name); } /** * Enumerate the headers defined by the holder. * @return A enumeration of header names, or <strong>null</strong> if no * header is defined. */ public Enumeration enumerateHeaders() { if ( headers == null ) return null; return headers.keys(); } /** * Get the remaining output of the stream. * This should be called only once header parsing is done. */ public InputStream getInputStream() { return parser.getInputStream(); } CGIHeaderHolder(MimeParser parser) { this.parser = parser; } } /** * Parsing the CGI output - Always create a CGIHeaderHolder. */ class CGIHeaderHolderFactory implements MimeParserFactory { /** * Create a new header holder to hold the parser's result. * @param parser The parser that has something to parse. * @return A MimeParserHandler compliant object. */ public MimeHeaderHolder createHeaderHolder(MimeParser parser) { return new CGIHeaderHolder(parser); } CGIHeaderHolderFactory() { } } /** * A simple process feeder class. */ class ProcessFeeder extends Thread { Process proc = null ; OutputStream out = null ; InputStream in = null ; int count = -1 ; public void run () { try { byte buffer[] = new byte[4096] ; int got = -1 ; // Send the data to the target process: if ( count >= 0 ) { while ( (count > 0) && ((got = in.read(buffer)) > 0) ) { out.write (buffer, 0, got) ; count -= got ; } } else { while ( (got = in.read(buffer)) > 0 ) { out.write (buffer, 0, got) ; } } } catch (Exception e) { System.out.println ("ProcessFeeder: caught exception !") ; e.printStackTrace() ; } finally { // Clean up the process: try { out.flush() ; } catch (IOException ex) {} try { out.close() ; } catch (IOException ex) {} try { proc.waitFor() ; } catch (Exception ex) {} } } ProcessFeeder (Process proc, InputStream in) { this (proc, in, -1) ; } ProcessFeeder (Process proc, InputStream in, int count) { this.proc = proc ; this.out = proc.getOutputStream() ; this.in = in ; this.count = count ; } } /** * Handle CGI scripts. */ public class CgiFrame extends HTTPFrame { private final static String STATE_EXTRA_PATH = "org.w3c.jigsaw.frames.CgiFrame.extraPath"; /** * Attribute index - The interpreter to use, if any. */ protected static int ATTR_INTERPRETER = -1; /** * Attribute index - The array of string that makes the command to run. */ protected static int ATTR_COMMAND = -1 ; /** * Attribute index - Does the script takes care of its headers ? */ protected static int ATTR_NOHEADER = -1 ; /** * Attribute index - Does the script generates the form on GET ? */ protected static int ATTR_GENERATES_FORM = -1 ; /** * Attribute index - Do DNS, to fill in REMOTE_HOST env var. */ protected static int ATTR_REMOTE_HOST = -1; /** * Attribute index - Turn the script in debug mode. */ protected static int ATTR_CGI_DEBUG = -1; static { Attribute a = null ; Class cls = null ; try { cls = Class.forName("org.w3c.jigsaw.frames.CgiFrame") ; } catch (Exception ex) { ex.printStackTrace() ; System.exit(1) ; } // The interpreter attribute: a = new StringAttribute("interpreter" , null , Attribute.EDITABLE); ATTR_INTERPRETER = AttributeRegistry.registerAttribute(cls, a); // The command attribute: a = new StringArrayAttribute("command" , null , Attribute.MANDATORY|Attribute.EDITABLE); ATTR_COMMAND = AttributeRegistry.registerAttribute(cls, a) ; // The noheader attribute: a = new BooleanAttribute("noheader" , Boolean.FALSE , Attribute.EDITABLE) ; ATTR_NOHEADER = AttributeRegistry.registerAttribute(cls, a) ; // The generates form attribute a = new BooleanAttribute("generates-form" , Boolean.TRUE , Attribute.EDITABLE) ; ATTR_GENERATES_FORM = AttributeRegistry.registerAttribute(cls, a); // Registerr the DODNS attribute. a = new BooleanAttribute("remote-host" , null , Attribute.EDITABLE); ATTR_REMOTE_HOST = AttributeRegistry.registerAttribute(cls, a); // Register the debug mode flag: a = new BooleanAttribute("cgi-debug" , Boolean.FALSE , Attribute.EDITABLE); ATTR_CGI_DEBUG = AttributeRegistry.registerAttribute(cls, a); } /** * Get the interpreter to use to execute the script. * This is most usefull for operating systems that don't have a * <code>!#</code> convention ala UNIX. * @return The interpreter to run the script. */ public String getInterpreter() { return getString(ATTR_INTERPRETER, null); } /** * Get the command string array. */ public String[] getCommand() { return (String[]) getValue(ATTR_COMMAND, null) ; } /** * Get the noheader flag. * @return The boolean value of the noheader flag. */ public boolean checkNoheaderFlag() { return getBoolean(ATTR_NOHEADER, false) ; } /** * Get the generates form flag. * @return The boolean value of the generates form flag. */ public boolean checkGeneratesFormFlag() { return getBoolean(ATTR_GENERATES_FORM, true) ; } /** * Get the remote host attribute value. * If turned on, this flag will enable the REMOTE_HOST env var computation. * @return A boolean. */ public boolean checkRemoteHost() { return getBoolean(ATTR_REMOTE_HOST, false); } /** * Get the CGI debug flag. * @return The boolean value of the CGI debug flag. */ public boolean checkCgiDebug() { return getBoolean(ATTR_CGI_DEBUG, false); } /** * Turn the given header name into it's env var canonical name. * This guy is crazy enough to run CGI scripts, he can pay for that * overhead. * @param name The header name. * @return A String giving the official env variable name for that header. */ public String getEnvName(String name) { int sl = name.length(); StringBuffer sb = new StringBuffer(5+sl); sb.append("HTTP_"); for (int i = 0 ; i < sl ; i++) { char ch = name.charAt(i); sb.append((ch == '-') ? '_' : Character.toUpperCase(ch)); } return sb.toString(); } /** * Handle the CGI script output. * This methods handles the CGI script output. Depending on the * value of the <strong>noheader</strong> attribute it either: * <ul> * <li>Sends back the script output directly,</li> * <li>Parses the script output, looking for a status header or a * location header, or a content-length header, or any combination * of those three. * </ul> * @param process The underlying CGI process. * @param request The processed request. * @exception ProtocolException If an HTTP error should be sent back to the * client. */ protected Reply handleCGIOutput (Process process, Request request) throws ProtocolException { // No header script don't deserve attention: if ( checkNoheaderFlag() ) { Reply reply = request.makeReply(HTTP.NOHEADER) ; reply.setStream (process.getInputStream()) ; return reply ; } // Check for debugging mode: if ( checkCgiDebug() ) { Reply reply = request.makeReply(HTTP.OK); reply.setContentType(MimeType.TEXT_PLAIN); reply.setStream(process.getInputStream()); return reply; } // We MUST parse at least one header: MimeParser p = new MimeParser(process.getInputStream(), new CGIHeaderHolderFactory()); Reply reply = null ; try { CGIHeaderHolder h = (CGIHeaderHolder) p.parse(); // Check for a status code: String svalue = h.getStatus(); String location = h.getLocation(); if ( svalue != null ) { int status = -1; try { status = Integer.parseInt(svalue); } catch (Exception ex) { // This script has emited an invalid status line: String msg = ("Emited an invalid status line ["+ svalue + "]."); getServer().errlog(this, msg); // Throw an HTTPException: reply = request.makeReply(HTTP.INTERNAL_SERVER_ERROR); reply.setContent("CGI script emited invalid status."); throw new HTTPException(reply); } reply = request.makeReply(status); } else { // No status code available, any location header ? reply = request.makeReply((location == null) ? HTTP.OK : HTTP.MOVED_TEMPORARILY); } // Set up the location header if needed: if ( location != null ) { try { reply.setLocation(new URL(getURL(request), location)); } catch (MalformedURLException ex) { // This should really not happen: getServer().errlog(this, "unable to create location url "+ location+ " in base "+getURL(request)); } } // And then, the remaining headers: Enumeration e = h.enumerateHeaders(); if ( e != null ) { while ( e.hasMoreElements() ) { String hname = (String) e.nextElement(); reply.setValue(hname, (String) h.getValue(hname)); } } reply.setStream(p.getInputStream()) ; } catch (IOException ex) { ex.printStackTrace(); } catch (MimeParserException ex) { // This script has generated invalid output: String msg = (getURL(request) +": emited invalid output ["+ ex.getMessage() +"]"); getServer().errlog(this, msg); // Throw an HTTPException: Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR) ; error.setContent("CGI error: unable to parse script headers.") ; throw new HTTPException (error) ; } return reply ; } /** * Add an enviornment binding to the given vector. * @param name The name of the enviornment variable. * @param val Its value. * @param into The vector to which accumulate bindings. */ private void addEnv (String name, String val, Vector into) { into.addElement (name+"="+val) ; } /** * Prepare the command to run for this CGI script, and run it. * @param request The request to handle. * @return The running CGI process object. * @exception HTTPException If we weren't able to build the command or * the environment. */ protected Process makeCgiCommand (Request request) throws ProtocolException, IOException { // Check the command attribute first: String query = null; String command[] = getCommand() ; if ( command == null ) { Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR) ; error.setContent("CgiResource mis-configured: it doesn't have a " + " command attribute"); throw new HTTPException(error); } // Ok: Vector env = new Vector(32) ; httpd server = request.getClient().getServer() ; InetAddress sadr = server.getInetAddress() ; // Specified environment variables: // We do not handle the following variables: // - PATH_TRANSLATED: I don't understand it // - REMOTE_IDENT: would require usage of IDENT protocol. // Authentification type, if any: String svalue = (String) request.getState(AuthFilter.STATE_AUTHTYPE); if ( svalue != null ) addEnv("AUTH_TYPE", svalue, env); // Content length, if available: svalue = request.getValue("content-length"); if ( svalue != null ) addEnv("CONTENT_LENGTH", svalue, env); // Content type, if available: svalue = request.getValue("content-type"); if ( svalue != null ) addEnv("CONTENT_TYPE", svalue, env); // The gateway interface, hopefully 1.1 ! addEnv ("GATEWAY_INTERFACE", "CGI/1.1", env) ; // The PATH_INFO, which I am afraid I still don't understand: svalue = (String) request.getState(STATE_EXTRA_PATH); if ( svalue == null ) addEnv ("PATH_INFO", "/", env) ; else addEnv ("PATH_INFO", svalue, env) ; // The query string: query = request.getQueryString(); if ( query != null ) addEnv("QUERY_STRING", query, env) ; // The remote client IP address: svalue = request.getClient().getInetAddress().toString(); addEnv ("REMOTE_ADDR", svalue, env); // Authentified user: svalue = (String) request.getState(AuthFilter.STATE_AUTHUSER); if ( svalue != null ) addEnv("REMOTE_USER", svalue, env); // Remote host name, if allowed: if ( checkRemoteHost() ) { String host = request.getClient().getInetAddress().getHostName(); addEnv("REMOTE_HOST", host, env); } // The request method: addEnv ("REQUEST_METHOD", request.getMethod(), env) ; // The script name : addEnv("SCRIPT_NAME", getURLPath(), env); // Server name: addEnv ("SERVER_NAME", getServer().getHost(), env) ; // Server port: svalue = Integer.toString(getServer().getLocalPort()); addEnv ("SERVER_PORT", svalue, env); // Server protocol: addEnv ("SERVER_PROTOCOL", request.getVersion(), env) ; // Server software: addEnv ("SERVER_SOFTWARE", server.getSoftware(), env) ; // All other request fields, yeah, let's lose even more time: Enumeration e = request.enumerateHeaderDescriptions(false); while ( e.hasMoreElements() ) { HeaderDescription d = (HeaderDescription) e.nextElement(); addEnv(getEnvName(d.getName()) , request.getHeaderValue(d).toString() , env); } // Command line: if ( query != null ) { String querycmd[] = new String[command.length+1] ; System.arraycopy(command, 0, querycmd, 0, command.length) ; querycmd[command.length] = query ; command = querycmd ; } String aenv[] = new String[env.size()] ; env.copyInto (aenv) ; // Run the process: if ( getInterpreter() != null ) { String run[] = new String[command.length+1]; run[0] = getInterpreter(); System.arraycopy(command, 0, run, 1, command.length); return Runtime.getRuntime().exec (run, aenv) ; } else { return Runtime.getRuntime().exec (command, aenv) ; } } /** * Lookup sub-resources. * Accumulate the remaning path in some special state of the request. * <p>This allows us to implement the <code>PATH_INFO</code> * CGI variable properly. * @param ls Current lookup state. * @param lr Lookup result under construction. * @return A boolean <strong>true</strong> if lookup should continue, * <strong>false</strong> otherwise. */ public boolean lookup(LookupState ls, LookupResult lr) throws ProtocolException { // Get the extra path information: String extraPath = ls.getRemainingPath(true); if ((extraPath == null) || extraPath.equals("")) extraPath = "/"; // Keep this path info into the request, if possible: Request request = (Request) ls.getRequest(); if ( request != null ) request.setState(STATE_EXTRA_PATH, extraPath); lr.setTarget(getResource().getResourceReference()); return super.lookup(ls, lr); } /** * GET method implementation. * this method is splitted into two cases: * <p>If the resource is able to generates its form, than run the script * to emit the form. Otherwsie, use our super class (FileResource) ability * to send the file that contains the form. * <p>Note that there is no need to feed the underlying process with * data in the GET case. * @param request The request to handle. * @exception ProtocolException If processing the request failed. */ public Reply get(Request request) throws ProtocolException, NotAProtocolException { if ( ! checkGeneratesFormFlag() ) return super.get (request) ; Process process = null ; try { process = makeCgiCommand (request) ; } catch (IOException e) { Reply error = request.makeReply(HTTP.NOT_FOUND) ; error.setContent("The resource's script wasn't found.") ; throw new HTTPException (error) ; } return handleCGIOutput (process, request) ; } /** * Handle the POST method according to CGI/1.1 specification. * The request body is sent back to the launched CGI script, as is, and * the script output is handled by the handleCGIOutput method. * @param request The request to process. * @exception ProtocolException If the processing failed. */ public Reply post(Request request) throws ProtocolException, NotAProtocolException { Process process = null ; // Launch the CGI process: try { process = makeCgiCommand(request) ; } catch (IOException ex) { // The process wasn't executable, emit a errlog message: String msg = ("The process "+ getCommand()[0] +" couldn't be executed ["+ ex.getMessage() + "]"); getServer().errlog(this, msg); // Throw an internal server error: Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR) ; error.setContent("CGI script is misconfigured."); throw new HTTPException (error) ; } // Now feed the process: try { // Send the 100 status code: Client client = request.getClient(); if ( client != null ) client.sendContinue(); InputStream in = request.getInputStream(); if ( in == null ) { // There was no input to that CCI, close process stream process.getOutputStream().close(); } else { // Some input to feed the process with: (new ProcessFeeder(process, in)).start(); } } catch (IOException ex) { // This is most probably a bad request: Reply error = request.makeReply(HTTP.BAD_REQUEST); error.setContent("The request didn't have a valid input."); throw new HTTPException(error); } return handleCGIOutput(process, request); } /** * At register time, if no command, use a suitable default. * THis method will set the command to the identifier, if it is not * provided. * @param values Default attribute values. */ public void registerResource(FramedResource resource) { super.registerResource(resource); //if no command is specified look for a file resource attached //and get its File absolute path if available. if (getCommand() == null) { if (getFileResource() != null) { if (getFileResource().getFile() != null) { String cmd[] = new String[1]; cmd[0] = getFileResource().getFile().getAbsolutePath(); setValue(ATTR_COMMAND, cmd); } } } } }
Received on Tuesday, 10 March 1998 11:58:54 UTC