001: // CgiServlet - runs CGI programs
002: //
003: // Copyright (C)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
004: //
005: // Redistribution and use in source and binary forms, with or without
006: // modification, are permitted provided that the following conditions
007: // are met:
008: // 1. Redistributions of source code must retain the above copyright
009: // notice, this list of conditions and the following disclaimer.
010: // 2. Redistributions in binary form must reproduce the above copyright
011: // notice, this list of conditions and the following disclaimer in the
012: // documentation and/or other materials provided with the distribution.
013: //
014: // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
015: // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
016: // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
017: // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
018: // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
019: // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
020: // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
021: // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
022: // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
023: // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
024: // SUCH DAMAGE.
025: //
026: // Visit the ACME Labs Java page for up-to-date versions of this and other
027: // fine Java utilities: http://www.acme.com/java/
028:
029: package Acme.Serve;
030:
031: import java.io.*;
032: import java.util.*;
033: import javax.servlet.*;
034: import javax.servlet.http.*;
035:
036: /// Runs CGI programs.
037: // <P>
038: // Note: although many implementations of CGI set the working directory of
039: // the subprocess to be the directory containing the executable file, the
040: // CGI spec actually says nothing about the working directory. Since
041: // Java has no method for setting the working directory, this implementation
042: // does not set it.
043: // <P>
044: // <A HREF="/resources/classes/Acme/Serve/CgiServlet.java">Fetch the software.</A><BR>
045: // <A HREF="/resources/classes/Acme.tar.Z">Fetch the entire Acme package.</A>
046: // <P>
047: // @see Acme.Serve.Serve
048:
049: public class CgiServlet extends HttpServlet {
050:
051: /// Returns a string containing information about the author, version, and
052: // copyright of the servlet.
053: public String getServletInfo() {
054: return "runs CGI programs";
055: }
056:
057: /// Services a single request from the client.
058: // @param req the servlet request
059: // @param req the servlet response
060: // @exception ServletException when an exception has occurred
061: public void service(HttpServletRequest req, HttpServletResponse res)
062: throws ServletException, IOException {
063: if (!(req.getMethod().equalsIgnoreCase("get") || req
064: .getMethod().equalsIgnoreCase("post"))) {
065: res.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
066: return;
067: }
068: dispatchPathname(req, res, getServletContext().getRealPath(
069: req.getServletPath() + req.getPathInfo()));
070: }
071:
072: private void dispatchPathname(HttpServletRequest req,
073: HttpServletResponse res, String path) throws IOException {
074: if (new File(path).exists())
075: serveFile(req, res, path);
076: else
077: res.sendError(HttpServletResponse.SC_NOT_FOUND);
078: }
079:
080: private void serveFile( HttpServletRequest req, HttpServletResponse res, String path ) throws IOException
081: {
082: String queryString = req.getQueryString();
083: int contentLength = req.getContentLength();
084: int c;
085:
086: log( "running " + path );
087:
088: // Make argument list.
089: Vector argVec = new Vector();
090: argVec.addElement( path );
091: if ( queryString != null && queryString.indexOf( "=" ) == -1 )
092: {
093: Enumeration enum = new StringTokenizer( queryString, "+" );
094: while ( enum.hasMoreElements() )
095: argVec.addElement( (String) enum.nextElement() );
096: }
097: String argList[] = makeList( argVec );
098:
099: // Make environment list.
100: Vector envVec = new Vector();
101: envVec.addElement( makeEnv(
102: "PATH", "/usr/local/bin:/usr/ucb:/bin:/usr/bin" ) );
103: envVec.addElement( makeEnv( "GATEWAY_INTERFACE", "CGI/1.1" ) );
104: envVec.addElement( makeEnv(
105: "SERVER_SOFTWARE", getServletContext().getServerInfo() ) );
106: envVec.addElement( makeEnv( "SERVER_NAME", req.getServerName() ) );
107: envVec.addElement( makeEnv(
108: "SERVER_PORT", Integer.toString( req.getServerPort() ) ) );
109: envVec.addElement( makeEnv( "REMOTE_ADDR", req.getRemoteAddr() ) );
110: envVec.addElement( makeEnv( "REMOTE_HOST", req.getRemoteHost() ) );
111: envVec.addElement( makeEnv( "REQUEST_METHOD", req.getMethod() ) );
112: if ( contentLength != -1 )
113: envVec.addElement( makeEnv(
114: "CONTENT_LENGTH", Integer.toString( contentLength ) ) );
115: if ( req.getContentType() != null )
116: envVec.addElement( makeEnv(
117: "CONTENT_TYPE", req.getContentType() ) );
118: envVec.addElement( makeEnv( "SCRIPT_NAME", req.getServletPath() ) );
119: if ( req.getPathInfo() != null )
120: envVec.addElement( makeEnv( "PATH_INFO", req.getPathInfo() ) );
121: if ( req.getPathTranslated() != null )
122: envVec.addElement( makeEnv(
123: "PATH_TRANSLATED", req.getPathTranslated() ) );
124: if ( queryString != null )
125: envVec.addElement( makeEnv( "QUERY_STRING", queryString ) );
126: envVec.addElement( makeEnv( "SERVER_PROTOCOL", req.getProtocol() ) );
127: if ( req.getRemoteUser() != null )
128: envVec.addElement( makeEnv( "REMOTE_USER", req.getRemoteUser() ) );
129: if ( req.getAuthType() != null )
130: envVec.addElement( makeEnv( "AUTH_TYPE", req.getAuthType() ) );
131: Enumeration enum = req.getHeaderNames();
132: while ( enum.hasMoreElements() )
133: {
134: String name = (String) enum.nextElement();
135: String value = req.getHeader( name );
136: if ( value == null )
137: value = "";
138: envVec.addElement( makeEnv(
139: "HTTP_" + name.toUpperCase().replace( '-', '_' ), value ) );
140: }
141: String envList[] = makeList( envVec );
142:
143: // Start the command.
144: Process proc = Runtime.getRuntime().exec( argList, envList );
145:
146: try
147: {
148: // If it's a POST, copy the request data to the process.
149: if ( req.getMethod().equalsIgnoreCase( "post" ) )
150: {
151: InputStream reqIn = req.getInputStream();
152: OutputStream procOut = proc.getOutputStream();
153: for ( int i = 0; i < contentLength; ++i )
154: {
155: c = reqIn.read();
156: if ( c == -1 )
157: break;
158: procOut.write( c );
159: }
160: procOut.close();
161: }
162:
163: // Now read the response from the process.
164: BufferedReader procIn = new BufferedReader( new InputStreamReader(
165: proc.getInputStream() ) );
166: OutputStream resOut = res.getOutputStream();
167: // Some of the headers have to be intercepted and handled.
168: boolean firstLine = true;
169: while ( true )
170: {
171: String line = procIn.readLine();
172: if ( line == null )
173: break;
174: line = line.trim();
175: if ( line.equals( "" ) )
176: break;
177: int colon = line.indexOf( ":" );
178: if ( colon == -1 )
179: {
180: // No colon. If it's the first line, parse it for status.
181: if ( firstLine )
182: {
183: StringTokenizer tok = new StringTokenizer( line, " " );
184: try
185: {
186: switch( tok.countTokens() )
187: {
188: case 2:
189: tok.nextToken();
190: res.setStatus(
191: Integer.parseInt( tok.nextToken() ) );
192: break;
193: case 3:
194: tok.nextToken();
195: res.setStatus(
196: Integer.parseInt( tok.nextToken() ),
197: tok.nextToken() );
198: break;
199: }
200: }
201: catch ( NumberFormatException ignore ) {}
202: }
203: else
204: {
205: // No colon and it's not the first line? Ignore.
206: }
207: }
208: else
209: {
210: // There's a colon. Check for certain special headers.
211: String name = line.substring( 0, colon );
212: String value = line.substring( colon + 1 ).trim();
213: if ( name.equalsIgnoreCase( "Status" ) )
214: {
215: StringTokenizer tok = new StringTokenizer( value, " " );
216: try
217: {
218: switch( tok.countTokens() )
219: {
220: case 1:
221: res.setStatus(
222: Integer.parseInt( tok.nextToken() ) );
223: break;
224: case 2:
225: res.setStatus(
226: Integer.parseInt( tok.nextToken() ),
227: tok.nextToken() );
228: break;
229: }
230: }
231: catch ( NumberFormatException ignore ) {}
232: }
233: else if ( name.equalsIgnoreCase( "Content-type" ) )
234: {
235: res.setContentType( value );
236: }
237: else if ( name.equalsIgnoreCase( "Content-length" ) )
238: {
239: try
240: {
241: res.setContentLength( Integer.parseInt( value ) );
242: }
243: catch ( NumberFormatException ignore ) {}
244: }
245: else if ( name.equalsIgnoreCase( "Location" ) )
246: {
247: res.setStatus(
248: HttpServletResponse.SC_MOVED_TEMPORARILY );
249: res.setHeader( name, value );
250: }
251: else
252: {
253: // Not a special header. Just set it.
254: res.setHeader( name, value );
255: }
256: }
257: }
258: // Copy the rest of the data uninterpreted.
259: Acme.Utils.copyStream( procIn, resOut );
260: procIn.close();
261: resOut.close();
262: }
263: catch ( IOException e )
264: {
265: //res.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR );
266: // There's some weird bug in Java, when reading from a Process
267: // you get a spurious IOException. We have to ignore it.
268: }
269: }
270:
271: private static String makeEnv(String name, String value) {
272: return name + "=" + value;
273: }
274:
275: private static String[] makeList(Vector vec) {
276: String list[] = new String[vec.size()];
277: for (int i = 0; i < vec.size(); ++i)
278: list[i] = (String) vec.elementAt(i);
279: return list;
280: }
281:
282: }
|