KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mortbay > servlet > CGI


1 // ========================================================================
2
// A very basic CGI Servlet, for use, with Jetty
3
// (jetty.mortbay.org). It's heading towards CGI/1.1 compliance, but
4
// still lacks a few features - the basic stuff is here though...
5
// Copyright 2000 Julian Gosnell <jules_gosnell@yahoo.com> Released
6
// under the terms of the Jetty Licence.
7
// ========================================================================
8

9 // TODO
10
// - logging
11
// - child's stderr
12
// - exceptions should report to client via sendError()
13
// - tidy up
14

15 package org.mortbay.servlet;
16
17 import java.io.File JavaDoc;
18 import java.io.IOException JavaDoc;
19 import java.io.InputStream JavaDoc;
20 import java.io.OutputStream JavaDoc;
21 import java.util.Enumeration JavaDoc;
22 import java.util.HashMap JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.Map JavaDoc;
25
26 import javax.servlet.ServletException JavaDoc;
27 import javax.servlet.http.HttpServlet JavaDoc;
28 import javax.servlet.http.HttpServletRequest JavaDoc;
29 import javax.servlet.http.HttpServletResponse JavaDoc;
30
31 import org.apache.commons.logging.Log;
32 import org.mortbay.log.LogFactory;
33 import org.mortbay.http.HttpFields;
34 import org.mortbay.util.IO;
35 import org.mortbay.util.LineInput;
36 import org.mortbay.util.LogSupport;
37 import org.mortbay.util.StringUtil;
38
39 //-----------------------------------------------------------------------------
40
/** CGI Servlet.
41  *
42  * The cgi bin directory can be set with the cgibinResourceBase init
43  * parameter or it will default to the resource base of the context.
44  *
45  * The "commandPrefix" init parameter may be used to set a prefix to all
46  * commands passed to exec. This can be used on systems that need assistance
47  * to execute a particular file type. For example on windows this can be set
48  * to "perl" so that perl scripts are executed.
49  *
50  * The "Path" init param is passed to the exec environment as PATH.
51  * Note: Must be run unpacked somewhere in the filesystem.
52  *
53  * Any initParameter that starts with ENV_ is used to set an environment
54  * variable with the name stripped of the leading ENV_ and using the init
55  * parameter value.
56  *
57  * @version $Revision: 1.27 $
58  * @author Julian Gosnell
59  */

60 public class CGI extends HttpServlet JavaDoc
61 {
62     private static Log log = LogFactory.getLog(CGI.class);
63
64     protected File JavaDoc _docRoot;
65     protected String JavaDoc _path;
66     protected String JavaDoc _cmdPrefix;
67     protected EnvList _env;
68
69     /* ------------------------------------------------------------ */
70     public void init()
71         throws ServletException JavaDoc
72     {
73         _env= new EnvList();
74         _cmdPrefix=getInitParameter("commandPrefix");
75
76         String JavaDoc tmp = getInitParameter("cgibinResourceBase");
77         if (tmp==null)
78             tmp = getServletContext().getRealPath("/");
79
80         if(log.isDebugEnabled())log.debug("CGI: CGI bin "+tmp);
81
82         if (tmp==null)
83         {
84             log.warn("CGI: no CGI bin !");
85             throw new ServletException JavaDoc();
86         }
87
88         File JavaDoc dir = new File JavaDoc(tmp);
89         if (!dir.exists())
90         {
91             log.warn("CGI: CGI bin does not exist - "+dir);
92             throw new ServletException JavaDoc();
93         }
94
95         if (!dir.canRead())
96         {
97             log.warn("CGI: CGI bin is not readable - "+dir);
98             throw new ServletException JavaDoc();
99         }
100
101         if (!dir.isDirectory())
102         {
103             log.warn("CGI: CGI bin is not a directory - "+dir);
104             throw new ServletException JavaDoc();
105         }
106
107         try
108         {
109             _docRoot=dir.getCanonicalFile();
110             if(log.isDebugEnabled())log.debug("CGI: CGI bin accepted - "+_docRoot);
111         }
112         catch (IOException JavaDoc e)
113         {
114             log.warn("CGI: CGI bin failed - "+dir);
115             e.printStackTrace();
116             throw new ServletException JavaDoc();
117         }
118
119         _path=getInitParameter("Path");
120         if(log.isDebugEnabled())log.debug("CGI: PATH accepted - "+_path);
121         if (_path != null)
122             _env.set("PATH", _path);
123
124         Enumeration JavaDoc e= getInitParameterNames();
125         while (e.hasMoreElements())
126         {
127             String JavaDoc n= (String JavaDoc)e.nextElement();
128             if (n != null && n.startsWith("ENV_"))
129                 _env.set(n.substring(4),getInitParameter(n));
130         }
131     }
132
133     /* ------------------------------------------------------------ */
134     public void service(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res)
135         throws ServletException JavaDoc, IOException JavaDoc
136     {
137     String JavaDoc pathInContext =
138         StringUtil.nonNull(req.getServletPath()) +
139         StringUtil.nonNull(req.getPathInfo());
140
141     if(log.isDebugEnabled())log.debug("CGI: req.getContextPath() : "+req.getContextPath());
142         if(log.isDebugEnabled())log.debug("CGI: req.getServletPath() : "+req.getServletPath());
143         if(log.isDebugEnabled())log.debug("CGI: req.getPathInfo() : "+req.getPathInfo());
144         if(log.isDebugEnabled())log.debug("CGI: _docRoot : "+_docRoot);
145
146
147         // pathInContext may actually comprises scriptName/pathInfo...We will
148
// walk backwards up it until we find the script - the rest must
149
// be the pathInfo;
150

151         String JavaDoc both=pathInContext;
152         String JavaDoc first=both;
153         String JavaDoc last="";
154
155         File JavaDoc exe=new File JavaDoc(_docRoot, first);
156
157         while ((first.endsWith("/") || !exe.exists()) && first.length()>=0)
158         {
159             int index=first.lastIndexOf('/');
160
161             first=first.substring(0, index);
162             last=both.substring(index, both.length());
163             exe=new File JavaDoc(_docRoot, first);
164         }
165
166         if (first.length()==0 ||
167             !exe.exists() ||
168             !exe.getCanonicalPath().equals(exe.getAbsolutePath()) ||
169             exe.isDirectory())
170             res.sendError(404);
171         else
172         {
173             if(log.isDebugEnabled())log.debug("CGI: script is "+exe);
174             if(log.isDebugEnabled())log.debug("CGI: pathInfo is "+last);
175
176             exec(exe, last, req, res);
177         }
178     }
179
180     /* ------------------------------------------------------------ */
181     /*
182      * @param root
183      * @param path
184      * @param req
185      * @param res
186      * @exception IOException
187      */

188     private void exec(File JavaDoc command,
189                       String JavaDoc pathInfo,
190                       HttpServletRequest JavaDoc req,
191                       HttpServletResponse JavaDoc res)
192         throws IOException JavaDoc
193     {
194         String JavaDoc path=command.toString();
195         File JavaDoc dir=command.getParentFile();
196         if(log.isDebugEnabled())log.debug("CGI: execing: "+path);
197
198     EnvList env = new EnvList(_env);
199
200     // these ones are from "The WWW Common Gateway Interface Version 1.1"
201
// look at : http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html#6.1.1
202
env.set("AUTH_TYPE", req.getAuthType());
203     env.set("CONTENT_LENGTH", Integer.toString(req.getContentLength()));
204     env.set("CONTENT_TYPE", req.getContentType());
205     env.set("GATEWAY_INTERFACE", "CGI/1.1");
206     env.set("PATH_INFO", pathInfo);
207     env.set("PATH_TRANSLATED", req.getPathTranslated());
208     env.set("QUERY_STRING", req.getQueryString());
209     env.set("REMOTE_ADDR", req.getRemoteAddr());
210     env.set("REMOTE_HOST", req.getRemoteHost());
211     // The identity information reported about the connection by a
212
// RFC 1413 [11] request to the remote agent, if
213
// available. Servers MAY choose not to support this feature, or
214
// not to request the data for efficiency reasons.
215
// "REMOTE_IDENT" => "NYI"
216
env.set("REMOTE_USER", req.getRemoteUser());
217     env.set("REQUEST_METHOD", req.getMethod());
218     String JavaDoc scriptName = req.getRequestURI().substring(0,req.getRequestURI().length() - pathInfo.length());
219     env.set("SCRIPT_NAME",scriptName);
220     env.set("SCRIPT_FILENAME",getServletContext().getRealPath(scriptName));
221     env.set("SERVER_NAME", req.getServerName());
222     env.set("SERVER_PORT", Integer.toString(req.getServerPort()));
223     env.set("SERVER_PROTOCOL", req.getProtocol());
224         env.set("SERVER_SOFTWARE", getServletContext().getServerInfo());
225     Enumeration JavaDoc enm = req.getHeaderNames();
226     while (enm.hasMoreElements())
227     {
228         String JavaDoc name = (String JavaDoc) enm.nextElement();
229         String JavaDoc value = req.getHeader(name);
230         env.set("HTTP_" + name.toUpperCase().replace( '-', '_' ), value);
231     }
232
233     // these extra ones were from printenv on www.dev.nomura.co.uk
234
env.set("HTTPS", (req.isSecure()?"ON":"OFF"));
235     // "DOCUMENT_ROOT" => root + "/docs",
236
// "SERVER_URL" => "NYI - http://us0245",
237
// "TZ" => System.getProperty("user.timezone"),
238

239         // are we meant to decode args here ? or does the script get them
240
// via PATH_INFO ? if we are, they should be decoded and passed
241
// into exec here...
242

243         String JavaDoc execCmd=path;
244         if (execCmd.indexOf(" ")>=0)
245             execCmd="\""+execCmd+"\"";
246         if (_cmdPrefix!=null)
247             execCmd=_cmdPrefix+" "+execCmd;
248
249         Process JavaDoc p=dir==null
250             ?Runtime.getRuntime().exec(execCmd, env.getEnvArray())
251             :Runtime.getRuntime().exec(execCmd, env.getEnvArray(),dir);
252
253         // hook processes input to browser's output (async)
254
final InputStream JavaDoc inFromReq=req.getInputStream();
255         final OutputStream JavaDoc outToCgi=p.getOutputStream();
256         final int inputLength = req.getContentLength();
257
258         new Thread JavaDoc(new Runnable JavaDoc()
259             {
260                 public void run()
261                 {
262                     try{
263                         if (inputLength>0)
264                             IO.copy(inFromReq,outToCgi,inputLength);
265                         outToCgi.close();
266                     }
267                     catch(IOException JavaDoc e){LogSupport.ignore(log,e);}
268                 }
269             }).start();
270
271
272         // hook processes output to browser's input (sync)
273
// if browser closes stream, we should detect it and kill process...
274
try
275         {
276             // read any headers off the top of our input stream
277
LineInput li = new LineInput(p.getInputStream());
278             HttpFields fields=new HttpFields();
279             fields.read(li);
280
281             String JavaDoc ContentStatus = "Status";
282             String JavaDoc redirect = fields.get(HttpFields.__Location);
283             String JavaDoc status = fields.get(ContentStatus);
284
285             if (status!=null)
286             {
287                 log.debug("Found a Status header - setting status on response");
288                 fields.remove(ContentStatus);
289
290                 // NOTE: we ignore any reason phrase, otherwise we
291
// would need to use res.sendError() selectively.
292
int i = status.indexOf(' ');
293                 if (i>0)
294                     status = status.substring(0,i);
295
296                 res.setStatus(Integer.parseInt(status));
297             }
298
299             // copy remaining headers into response...
300
for (Iterator JavaDoc i=fields.iterator(); i.hasNext();)
301             {
302                 HttpFields.Entry e=(HttpFields.Entry)i.next();
303                 res.addHeader(e.getKey(),e.getValue());
304             }
305
306             if (status==null && redirect != null)
307             {
308                 // The CGI has set Location and is counting on us to do the redirect.
309
// See http://CGI-Spec.Golux.Com/draft-coar-cgi-v11-03-clean.html#7.2.1.2
310
if (!redirect.startsWith("http:/")&&!redirect.startsWith("https:/"))
311                     res.sendRedirect(redirect);
312                 else
313                     res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
314             }
315
316             // copy remains of input onto output...
317
IO.copy(li, res.getOutputStream());
318
319         p.waitFor();
320         int exitValue = p.exitValue();
321         if(log.isDebugEnabled())log.debug("CGI: p.exitValue(): " + exitValue);
322         if (0 != exitValue)
323         {
324         log.warn("Non-zero exit status ("+exitValue+
325                  ") from CGI program: "+path);
326         if (!res.isCommitted())
327             res.sendError(500, "Failed to exec CGI");
328         }
329         }
330         catch (IOException JavaDoc e)
331         {
332             // browser has probably closed its input stream - we
333
// terminate and clean up...
334
log.debug("CGI: Client closed connection!");
335         }
336     catch (InterruptedException JavaDoc ie)
337         {
338             log.debug("CGI: interrupted!");
339         }
340     finally
341     {
342             p.destroy();
343     }
344
345     if(log.isDebugEnabled())log.debug("CGI: Finished exec: " + p);
346     }
347
348
349     /* ------------------------------------------------------------ */
350     /** private utility class that manages the Environment passed
351      * to exec.
352      */

353     private static class EnvList
354     {
355         private Map JavaDoc envMap;
356
357         EnvList()
358         {
359             envMap= new HashMap JavaDoc();
360         }
361
362         EnvList(EnvList l)
363         {
364             envMap= new HashMap JavaDoc(l.envMap);
365         }
366
367     /** Set a name/value pair, null values will be treated as
368      * an empty String */

369     public void set(String JavaDoc name, String JavaDoc value) {
370             envMap.put(name, name + "=" + StringUtil.nonNull(value));
371     }
372
373     /** Get representation suitable for passing to exec. */
374     public String JavaDoc[] getEnvArray()
375     {
376             return (String JavaDoc[])envMap.values().toArray(new String JavaDoc[envMap.size()]);
377     }
378     }
379 }
380
Popular Tags