helma/src/Acme/Serve/CgiServlet.java
hns 89c38c6eee This commit was generated by cvs2svn to compensate for changes in r7,
which included commits to RCS files with non-trunk default branches.
2000-12-29 17:58:41 +00:00

313 lines
9.8 KiB
Java

// CgiServlet - runs CGI programs
//
// Copyright (C)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// Visit the ACME Labs Java page for up-to-date versions of this and other
// fine Java utilities: http://www.acme.com/java/
package Acme.Serve;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
/// Runs CGI programs.
// <P>
// Note: although many implementations of CGI set the working directory of
// the subprocess to be the directory containing the executable file, the
// CGI spec actually says nothing about the working directory. Since
// Java has no method for setting the working directory, this implementation
// does not set it.
// <P>
// <A HREF="/resources/classes/Acme/Serve/CgiServlet.java">Fetch the software.</A><BR>
// <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
// <P>
// @see Acme.Serve.Serve
public class CgiServlet extends HttpServlet
{
/// Returns a string containing information about the author, version, and
// copyright of the servlet.
public String getServletInfo()
{
return "runs CGI programs";
}
/// Services a single request from the client.
// @param req the servlet request
// @param req the servlet response
// @exception ServletException when an exception has occurred
public void service( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException
{
if ( ! ( req.getMethod().equalsIgnoreCase( "get" ) ||
req.getMethod().equalsIgnoreCase( "post" ) ) )
{
res.sendError( HttpServletResponse.SC_NOT_IMPLEMENTED );
return;
}
String path = req.getServletPath();
if ( path == null || path.charAt( 0 ) != '/' )
{
res.sendError( HttpServletResponse.SC_BAD_REQUEST );
return;
}
if ( path.indexOf( "/../" ) != -1 || path.endsWith( "/.." ) )
{
res.sendError( HttpServletResponse.SC_FORBIDDEN );
return;
}
// Make a version without the leading /.
String pathname = path.substring( 1 );
if ( pathname.length() == 0 )
pathname = "./";
dispatchPathname( req, res, path, pathname );
}
private void dispatchPathname( HttpServletRequest req, HttpServletResponse res, String path, String pathname ) throws IOException
{
String filename = pathname.replace( '/', File.separatorChar );
if ( filename.charAt( filename.length() - 1 ) == File.separatorChar )
filename = filename.substring( 0, filename.length() - 1 );
filename = getServletContext().getRealPath( filename );
File file = new File( filename );
if ( file.exists() )
serveFile( req, res, path, filename, file );
else
res.sendError( HttpServletResponse.SC_NOT_FOUND );
}
private void serveFile( HttpServletRequest req, HttpServletResponse res, String path, String filename, File file ) throws IOException
{
String queryString = req.getQueryString();
int contentLength = req.getContentLength();
int c;
log( "running " + path );
// Make argument list.
Vector argVec = new Vector();
argVec.addElement( filename );
if ( queryString != null && queryString.indexOf( "=" ) == -1 )
{
Enumeration enum = new StringTokenizer( queryString, "+" );
while ( enum.hasMoreElements() )
argVec.addElement( (String) enum.nextElement() );
}
String argList[] = makeList( argVec );
// Make environment list.
Vector envVec = new Vector();
envVec.addElement( makeEnv(
"PATH", "/usr/local/bin:/usr/ucb:/bin:/usr/bin" ) );
envVec.addElement( makeEnv( "GATEWAY_INTERFACE", "CGI/1.1" ) );
envVec.addElement( makeEnv(
"SERVER_SOFTWARE", getServletContext().getServerInfo() ) );
envVec.addElement( makeEnv( "SERVER_NAME", req.getServerName() ) );
envVec.addElement( makeEnv(
"SERVER_PORT", Integer.toString( req.getServerPort() ) ) );
envVec.addElement( makeEnv( "REMOTE_ADDR", req.getRemoteAddr() ) );
envVec.addElement( makeEnv( "REMOTE_HOST", req.getRemoteHost() ) );
envVec.addElement( makeEnv( "REQUEST_METHOD", req.getMethod() ) );
if ( contentLength != -1 )
envVec.addElement( makeEnv(
"CONTENT_LENGTH", Integer.toString( contentLength ) ) );
if ( req.getContentType() != null )
envVec.addElement( makeEnv(
"CONTENT_TYPE", req.getContentType() ) );
envVec.addElement( makeEnv( "SCRIPT_NAME", req.getServletPath() ) );
if ( req.getPathInfo() != null )
envVec.addElement( makeEnv( "PATH_INFO", req.getPathInfo() ) );
if ( req.getPathTranslated() != null )
envVec.addElement( makeEnv(
"PATH_TRANSLATED", req.getPathTranslated() ) );
if ( queryString != null )
envVec.addElement( makeEnv( "QUERY_STRING", queryString ) );
envVec.addElement( makeEnv( "SERVER_PROTOCOL", req.getProtocol() ) );
if ( req.getRemoteUser() != null )
envVec.addElement( makeEnv( "REMOTE_USER", req.getRemoteUser() ) );
if ( req.getAuthType() != null )
envVec.addElement( makeEnv( "AUTH_TYPE", req.getAuthType() ) );
Enumeration enum = req.getHeaderNames();
while ( enum.hasMoreElements() )
{
String name = (String) enum.nextElement();
String value = req.getHeader( name );
if ( value == null )
value = "";
envVec.addElement( makeEnv(
"HTTP_" + name.toUpperCase().replace( '-', '_' ), value ) );
}
String envList[] = makeList( envVec );
// Start the command.
Process proc = Runtime.getRuntime().exec( argList, envList );
try
{
// If it's a POST, copy the request data to the process.
if ( req.getMethod().equalsIgnoreCase( "post" ) )
{
InputStream reqIn = req.getInputStream();
OutputStream procOut = proc.getOutputStream();
for ( int i = 0; i < contentLength; ++i )
{
c = reqIn.read();
if ( c == -1 )
break;
procOut.write( c );
}
procOut.close();
}
// Now read the response from the process.
BufferedReader procIn = new BufferedReader( new InputStreamReader(
proc.getInputStream() ) );
OutputStream resOut = res.getOutputStream();
// Some of the headers have to be intercepted and handled.
boolean firstLine = true;
while ( true )
{
String line = procIn.readLine();
if ( line == null )
break;
line = line.trim();
if ( line.equals( "" ) )
break;
int colon = line.indexOf( ":" );
if ( colon == -1 )
{
// No colon. If it's the first line, parse it for status.
if ( firstLine )
{
StringTokenizer tok = new StringTokenizer( line, " " );
try
{
switch( tok.countTokens() )
{
case 2:
tok.nextToken();
res.setStatus(
Integer.parseInt( tok.nextToken() ) );
break;
case 3:
tok.nextToken();
res.setStatus(
Integer.parseInt( tok.nextToken() ),
tok.nextToken() );
break;
}
}
catch ( NumberFormatException ignore ) {}
}
else
{
// No colon and it's not the first line? Ignore.
}
}
else
{
// There's a colon. Check for certain special headers.
String name = line.substring( 0, colon );
String value = line.substring( colon + 1 ).trim();
if ( name.equalsIgnoreCase( "Status" ) )
{
StringTokenizer tok = new StringTokenizer( value, " " );
try
{
switch( tok.countTokens() )
{
case 1:
res.setStatus(
Integer.parseInt( tok.nextToken() ) );
break;
case 2:
res.setStatus(
Integer.parseInt( tok.nextToken() ),
tok.nextToken() );
break;
}
}
catch ( NumberFormatException ignore ) {}
}
else if ( name.equalsIgnoreCase( "Content-type" ) )
{
res.setContentType( value );
}
else if ( name.equalsIgnoreCase( "Content-length" ) )
{
try
{
res.setContentLength( Integer.parseInt( value ) );
}
catch ( NumberFormatException ignore ) {}
}
else if ( name.equalsIgnoreCase( "Location" ) )
{
res.setStatus(
HttpServletResponse.SC_MOVED_TEMPORARILY );
res.setHeader( name, value );
}
else
{
// Not a special header. Just set it.
res.setHeader( name, value );
}
}
}
// Copy the rest of the data uninterpreted.
Acme.Utils.copyStream( procIn, resOut );
procIn.close();
resOut.close();
}
catch ( IOException e )
{
//res.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR );
// There's some weird bug in Java, when reading from a Process
// you get a spurious IOException. We have to ignore it.
}
}
private static String makeEnv( String name, String value )
{
return name + "=" + value;
}
private static String[] makeList( Vector vec )
{
String list[] = new String[vec.size()];
for ( int i = 0; i < vec.size(); ++i )
list[i] = (String) vec.elementAt( i );
return list;
}
}