001: /*
002: * CgiHandler.java
003: *
004: * Brazil project web application Framework,
005: * export version: 1.1
006: * Copyright (c) 1998-2000 Sun Microsystems, Inc.
007: *
008: * Sun Public License Notice
009: *
010: * The contents of this file are subject to the Sun Public License Version
011: * 1.0 (the "License"). You may not use this file except in compliance with
012: * the License. A copy of the License is included as the file "license.terms",
013: * and also available at http://www.sun.com/
014: *
015: * The Original Code is from:
016: * Brazil project web application Framework release 1.1.
017: * The Initial Developer of the Original Code is: suhler.
018: * Portions created by suhler are Copyright (C) Sun Microsystems, Inc.
019: * All Rights Reserved.
020: *
021: * Contributor(s): cstevens, suhler.
022: *
023: * Version: 1.17
024: * Created by suhler on 98/09/14
025: * Last modified by suhler on 00/12/11 13:26:51
026: */
027:
028: package sunlabs.brazil.handler;
029:
030: import java.io.BufferedInputStream;
031: import java.io.ByteArrayOutputStream;
032: import java.io.DataInputStream;
033: import java.io.File;
034: import java.io.InputStream;
035: import java.io.OutputStream;
036: import java.net.InetAddress;
037: import java.net.Socket;
038: import java.util.Enumeration;
039: import java.util.Hashtable;
040: import java.util.Vector;
041: import sunlabs.brazil.server.Handler;
042: import sunlabs.brazil.server.Request;
043: import sunlabs.brazil.server.Server;
044:
045: /**
046: * Handler for implementing cgi/1.1 interface.
047: * This implementation allows either suffix matching (e.g. .cgi) to identify
048: * cgi scripts, or prefix matching (e.g. /bgi-bin). Defaults to "/".
049: * All output from the cgi script is buffered (e.g. chunked encoding is
050: * not supported).
051: * <br>
052: * NOTE: this handler does not "cd" to the script directory before
053: * running the script; The code to "cd" in Java is too messy to bother.
054: * <p>
055: * The following request properties are used:
056: * <dl class=props>
057: * <dt>root <dd> The document root for cgi files
058: * <dt>suffix <dd> The suffix for cgi files (defaults to .cgi)
059: * <dt>prefix <dd> The prefix for all cgi files (e.g. /cgi-bin)
060: * <dt>custom <dd> set to "true" to enable custom environment variables.
061: * If set, all server properties starting with this handler's
062: * prefix are placed into the environment with the name:
063: * <code>CONFIG_<i>name</i></code>, where <i>name</i> is the
064: * property key, in upper case, with the prefix removed.
065: * This allows cgi scripts to be customized in the server's
066: * configuration file.
067: * </dl>
068: *
069: * @author Stephen Uhler
070: * @version 1.17, 00/12/11
071: */
072:
073: public class CgiHandler implements Handler {
074: private String propsPrefix; // string prefix in properties table
075: private int port; // The listening port
076: private String protocol; // the access protocol http/https
077: private String hostname; // My hostname
078: private static final String ROOT = "root"; // property for document root
079: private static final String SUFFIX = "suffix"; // property for suffix string
080: private static final String PREFIX = "prefix"; // All cgi scripts must start with this
081: private static final String CUSTOM = "custom"; // add custom query variables
082:
083: private static String software = "Mini Java CgiHandler 0.1";
084: private static Hashtable envMap; // environ maps
085:
086: /**
087: * construct table of CGI environment variables that need special handling
088: */
089:
090: static {
091: envMap = new Hashtable(2);
092: envMap.put("content-length", "CONTENT_LENGTH");
093: envMap.put("content-type", "CONTENT_TYPE");
094: }
095:
096: public CgiHandler() {
097: }
098:
099: /**
100: * One time initialization. The handler configuration properties are
101: * extracted and set in
102: * {@link #respond(Request)} to allow
103: * upstream handlers to modify the parameters.
104: */
105:
106: public boolean init(Server server, String prefix) {
107: propsPrefix = prefix;
108: port = server.listen.getLocalPort();
109: hostname = server.hostName;
110: protocol = server.protocol;
111: return true;
112: }
113:
114: /**
115: * Dispatch and handle the CGI request. Gets called on ALL requests.
116: * Set up the environment, exec the process, and deal
117: * appropriately with the input and output.
118: *
119: * In this implementation, all cgi script files must end with a
120: * standard suffix, although the suffix may omitted from the url.
121: * The url /main/do/me/too?a=b will look, starting in DocRoot,
122: * for main.cgi, main/do.cgi, etc until a matching file is found.
123: * <p>
124: * Input parameters examined in the request properties:
125: * <dl>
126: * <dt>Suffix<dd>The suffix for all cgi scripts (defaults to .cgi)
127: * <dt>DocRoot<dd>The document root, for locating the script.
128: * </dl>
129: */
130:
131: public boolean respond(Request request) {
132: String[] command; // The command to run
133: Process cgi; // The result of the cgi process
134:
135: // Find the cgi script associated with this request.
136: // + turn the url into a file name
137: // + search path until a script is found
138:
139: String url = request.url;
140: String prefix = request.props.getProperty(propsPrefix + PREFIX,
141: "/");
142:
143: if (!url.startsWith(prefix)) {
144: return false;
145: }
146:
147: boolean useCustom = !request.props.getProperty(
148: propsPrefix + CUSTOM, "").equals("");
149: String suffix = request.props.getProperty(propsPrefix + SUFFIX,
150: ".cgi");
151: String root = request.props.getProperty(propsPrefix + ROOT,
152: request.props.getProperty(ROOT, "."));
153: request.log(Server.LOG_DIAGNOSTIC, propsPrefix + " suffix="
154: + suffix + " root=" + root + " url: " + url);
155:
156: int start = 1;
157: int end = 0;
158: File name = null;
159: while (end < url.length()) {
160: end = url.indexOf(File.separatorChar, start);
161: if (end < 0) {
162: end = url.length();
163: }
164: String s = url.substring(1, end);
165: if (!s.endsWith(suffix)) {
166: s += suffix;
167: }
168: name = new File(root, s);
169: request.log(Server.LOG_DIAGNOSTIC, propsPrefix
170: + " looking for: " + name);
171: if (name.isFile()) {
172: break;
173: }
174: name = null;
175: start = end + 1;
176: }
177:
178: if (name == null) {
179: return false;
180: }
181:
182: // Formulate the command. Look at the query and check for an =
183: // If no '=', then use '+' as an argument delimeter
184:
185: String query = request.query;
186:
187: if (query.indexOf("=") == -1) { // need args
188: command = new String[2];
189: command[1] = query; // XXX this is wrong
190: } else { // no args
191: command = new String[1];
192: }
193: command[0] = name.getAbsolutePath();
194: request.log(Server.LOG_DIAGNOSTIC, propsPrefix + " command= "
195: + command[0]);
196:
197: /*
198: * Build the environment string. First, get all the http headers
199: * most are transferred directly to the environment, some are
200: * handled specially. Multiple headers with the same name are not
201: * handled properly.
202: */
203:
204: Vector env = new Vector();
205: Enumeration keys = request.headers.keys();
206: while (keys.hasMoreElements()) {
207: String key = (String) keys.nextElement();
208: String special = (String) envMap.get(key.toLowerCase());
209: if (special != null) {
210: env
211: .addElement(special + "="
212: + request.headers.get(key));
213: } else {
214: env.addElement("HTTP_"
215: + key.toUpperCase().replace('-', '_') + "="
216: + request.headers.get(key));
217: }
218: }
219:
220: // Add in the rest of them
221:
222: env.addElement("GATEWAY_INTERFACE=CGI/1.1");
223: env.addElement("SERVER_SOFTWARE=" + software);
224: env.addElement("SERVER_NAME=" + hostname);
225: env.addElement("PATH_INFO=" + url.substring(end));
226:
227: String pre = url.substring(0, end);
228: if (pre.endsWith(suffix)) {
229: env.addElement("SCRIPT_NAME=" + pre);
230: } else {
231: env.addElement("SCRIPT_NAME=" + pre + suffix);
232: }
233: env.addElement("SERVER_PORT=" + port);
234: env
235: .addElement("REMOTE_ADDR="
236: + request.getSocket().getInetAddress()
237: .getHostAddress());
238: env.addElement("PATH_TRANSLATED=" + root + url.substring(end));
239: env.addElement("REQUEST_METHOD=" + request.method);
240: env.addElement("SERVER_PROTOCOL=" + request.protocol);
241: env.addElement("QUERY_STRING=" + request.query);
242:
243: if (protocol.equals("https")) {
244: env.addElement("HTTPS=on");
245: }
246: env.addElement("SERVER_URL=" + request.serverUrl());
247:
248: /*
249: * add in the "custom" environment variables, if requested
250: */
251:
252: if (useCustom) {
253: int len = propsPrefix.length();
254: keys = request.props.propertyNames();
255: while (keys.hasMoreElements()) {
256: String key = (String) keys.nextElement();
257: if (key.startsWith(propsPrefix)) {
258: env.addElement("CONFIG_"
259: + key.substring(len).toUpperCase() + "="
260: + request.props.getProperty(key, null));
261: }
262: }
263: env.addElement("CONFIG_PREFIX=" + propsPrefix);
264: }
265:
266: String environ[] = new String[env.size()];
267: env.copyInto(environ);
268: request
269: .log(Server.LOG_DIAGNOSTIC, propsPrefix + " ENV= "
270: + env);
271:
272: // Run the script
273:
274: try {
275: cgi = Runtime.getRuntime().exec(command, environ);
276: DataInputStream in = new DataInputStream(
277: new BufferedInputStream(cgi.getInputStream()));
278:
279: // If we have data, send it to the process
280:
281: if (request.postData != null) {
282: OutputStream toGci = cgi.getOutputStream();
283:
284: toGci.write(request.postData, 0,
285: request.postData.length);
286: toGci.close();
287: }
288:
289: // Now get the output of the cgi script. Start by reading the
290: // "mini header", then just copy the rest
291:
292: String head;
293: String type = "text/html";
294: int status = 200;
295: while (true) {
296: head = in.readLine();
297: if (head == null || head.length() == 0) {
298: break;
299: }
300: int colonIndex = head.indexOf(':');
301: if (colonIndex < 0) {
302: request.sendError(500,
303: "Missing header from cgi output");
304: return true;
305: }
306: String lower = head.toLowerCase();
307: if (lower.startsWith("status:")) {
308: try {
309: status = Integer.parseInt(head.substring(
310: colonIndex + 1).trim());
311: } catch (NumberFormatException e) {
312: }
313: } else if (lower.startsWith("content-type:")) {
314: type = head.substring(colonIndex + 1).trim();
315: } else if (lower.startsWith("location:")) {
316: status = 302;
317: request.addHeader(head);
318: } else {
319: request.addHeader(head);
320: }
321: }
322:
323: /*
324: * Now copy the rest of the data into a buffer, so we can count it.
325: * we should be doing chunked encoding for 1.1 capable clients XXX
326: */
327:
328: ByteArrayOutputStream buff = new ByteArrayOutputStream();
329: int c;
330: while ((c = in.read()) >= 0) {
331: buff.write(c);
332: }
333:
334: request.sendHeaders(status, type, buff.size());
335: buff.writeTo(request.out);
336: request.log(Server.LOG_DIAGNOSTIC, propsPrefix,
337: "Cgi output " + buff.size());
338: cgi.waitFor();
339: } catch (Exception e) {
340: System.out.println("oops: " + e);
341: e.printStackTrace();
342: }
343: return true;
344: }
345: }
|