KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > catalina > servlets > CGIServlet


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18
19 package org.apache.catalina.servlets;
20
21 import java.io.BufferedOutputStream JavaDoc;
22 import java.io.BufferedReader JavaDoc;
23 import java.io.File JavaDoc;
24 import java.io.FileOutputStream JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.io.InputStream JavaDoc;
27 import java.io.InputStreamReader JavaDoc;
28 import java.io.OutputStream JavaDoc;
29 import java.io.UnsupportedEncodingException JavaDoc;
30 import java.net.URLDecoder JavaDoc;
31 import java.util.ArrayList JavaDoc;
32 import java.util.Date JavaDoc;
33 import java.util.Enumeration JavaDoc;
34 import java.util.Hashtable JavaDoc;
35 import java.util.Locale JavaDoc;
36 import java.util.StringTokenizer JavaDoc;
37 import java.util.Vector JavaDoc;
38
39 import javax.servlet.ServletConfig JavaDoc;
40 import javax.servlet.ServletContext JavaDoc;
41 import javax.servlet.ServletException JavaDoc;
42 import javax.servlet.ServletOutputStream JavaDoc;
43 import javax.servlet.UnavailableException JavaDoc;
44 import javax.servlet.http.Cookie JavaDoc;
45 import javax.servlet.http.HttpServlet JavaDoc;
46 import javax.servlet.http.HttpServletRequest JavaDoc;
47 import javax.servlet.http.HttpServletResponse JavaDoc;
48 import javax.servlet.http.HttpSession JavaDoc;
49
50 import org.apache.catalina.Globals;
51 import org.apache.catalina.util.IOTools;
52
53
54 /**
55  * CGI-invoking servlet for web applications, used to execute scripts which
56  * comply to the Common Gateway Interface (CGI) specification and are named
57  * in the path-info used to invoke this servlet.
58  *
59  * <p>
60  * <i>Note: This code compiles and even works for simple CGI cases.
61  * Exhaustive testing has not been done. Please consider it beta
62  * quality. Feedback is appreciated to the author (see below).</i>
63  * </p>
64  * <p>
65  *
66  * <b>Example</b>:<br>
67  * If an instance of this servlet was mapped (using
68  * <code>&lt;web-app&gt;/WEB-INF/web.xml</code>) to:
69  * </p>
70  * <p>
71  * <code>
72  * &lt;web-app&gt;/cgi-bin/*
73  * </code>
74  * </p>
75  * <p>
76  * then the following request:
77  * </p>
78  * <p>
79  * <code>
80  * http://localhost:8080/&lt;web-app&gt;/cgi-bin/dir1/script/pathinfo1
81  * </code>
82  * </p>
83  * <p>
84  * would result in the execution of the script
85  * </p>
86  * <p>
87  * <code>
88  * &lt;web-app-root&gt;/WEB-INF/cgi/dir1/script
89  * </code>
90  * </p>
91  * <p>
92  * with the script's <code>PATH_INFO</code> set to <code>/pathinfo1</code>.
93  * </p>
94  * <p>
95  * Recommendation: House all your CGI scripts under
96  * <code>&lt;webapp&gt;/WEB-INF/cgi</code>. This will ensure that you do not
97  * accidentally expose your cgi scripts' code to the outside world and that
98  * your cgis will be cleanly ensconced underneath the WEB-INF (i.e.,
99  * non-content) area.
100  * </p>
101  * <p>
102  * The default CGI location is mentioned above. You have the flexibility to
103  * put CGIs wherever you want, however:
104  * </p>
105  * <p>
106  * The CGI search path will start at
107  * webAppRootDir + File.separator + cgiPathPrefix
108  * (or webAppRootDir alone if cgiPathPrefix is
109  * null).
110  * </p>
111  * <p>
112  * cgiPathPrefix is defined by setting
113  * this servlet's cgiPathPrefix init parameter
114  * </p>
115  *
116  * <p>
117  *
118  * <B>CGI Specification</B>:<br> derived from
119  * <a HREF="http://cgi-spec.golux.com">http://cgi-spec.golux.com</a>.
120  * A work-in-progress & expired Internet Draft. Note no actual RFC describing
121  * the CGI specification exists. Where the behavior of this servlet differs
122  * from the specification cited above, it is either documented here, a bug,
123  * or an instance where the specification cited differs from Best
124  * Community Practice (BCP).
125  * Such instances should be well-documented here. Please email the
126  * <a HREF="mailto:tomcat-dev@jakarta.apache.org">Jakarta Tomcat group [tomcat-dev@jakarta.apache.org]</a>
127  * with amendments.
128  *
129  * </p>
130  * <p>
131  *
132  * <b>Canonical metavariables</b>:<br>
133  * The CGI specification defines the following canonical metavariables:
134  * <br>
135  * [excerpt from CGI specification]
136  * <PRE>
137  * AUTH_TYPE
138  * CONTENT_LENGTH
139  * CONTENT_TYPE
140  * GATEWAY_INTERFACE
141  * PATH_INFO
142  * PATH_TRANSLATED
143  * QUERY_STRING
144  * REMOTE_ADDR
145  * REMOTE_HOST
146  * REMOTE_IDENT
147  * REMOTE_USER
148  * REQUEST_METHOD
149  * SCRIPT_NAME
150  * SERVER_NAME
151  * SERVER_PORT
152  * SERVER_PROTOCOL
153  * SERVER_SOFTWARE
154  * </PRE>
155  * <p>
156  * Metavariables with names beginning with the protocol name (<EM>e.g.</EM>,
157  * "HTTP_ACCEPT") are also canonical in their description of request header
158  * fields. The number and meaning of these fields may change independently
159  * of this specification. (See also section 6.1.5 [of the CGI specification].)
160  * </p>
161  * [end excerpt]
162  *
163  * </p>
164  * <h2> Implementation notes</h2>
165  * <p>
166  *
167  * <b>standard input handling</b>: If your script accepts standard input,
168  * then the client must start sending input within a certain timeout period,
169  * otherwise the servlet will assume no input is coming and carry on running
170  * the script. The script's the standard input will be closed and handling of
171  * any further input from the client is undefined. Most likely it will be
172  * ignored. If this behavior becomes undesirable, then this servlet needs
173  * to be enhanced to handle threading of the spawned process' stdin, stdout,
174  * and stderr (which should not be too hard).
175  * <br>
176  * If you find your cgi scripts are timing out receiving input, you can set
177  * the init parameter <code></code> of your webapps' cgi-handling servlet
178  * to be
179  * </p>
180  * <p>
181  *
182  * <b>Metavariable Values</b>: According to the CGI specificion,
183  * implementations may choose to represent both null or missing values in an
184  * implementation-specific manner, but must define that manner. This
185  * implementation chooses to always define all required metavariables, but
186  * set the value to "" for all metavariables whose value is either null or
187  * undefined. PATH_TRANSLATED is the sole exception to this rule, as per the
188  * CGI Specification.
189  *
190  * </p>
191  * <p>
192  *
193  * <b>NPH -- Non-parsed-header implementation</b>: This implementation does
194  * not support the CGI NPH concept, whereby server ensures that the data
195  * supplied to the script are preceisely as supplied by the client and
196  * unaltered by the server.
197  * </p>
198  * <p>
199  * The function of a servlet container (including Tomcat) is specifically
200  * designed to parse and possible alter CGI-specific variables, and as
201  * such makes NPH functionality difficult to support.
202  * </p>
203  * <p>
204  * The CGI specification states that compliant servers MAY support NPH output.
205  * It does not state servers MUST support NPH output to be unconditionally
206  * compliant. Thus, this implementation maintains unconditional compliance
207  * with the specification though NPH support is not present.
208  * </p>
209  * <p>
210  *
211  * The CGI specification is located at
212  * <a HREF="http://cgi-spec.golux.com">http://cgi-spec.golux.com</a>.
213  *
214  * </p>
215  * <p>
216  * <h3>TODO:</h3>
217  * <ul>
218  * <li> Support for setting headers (for example, Location headers don't work)
219  * <li> Support for collapsing multiple header lines (per RFC 2616)
220  * <li> Ensure handling of POST method does not interfere with 2.3 Filters
221  * <li> Refactor some debug code out of core
222  * <li> Ensure header handling preserves encoding
223  * <li> Possibly rewrite CGIRunner.run()?
224  * <li> Possibly refactor CGIRunner and CGIEnvironment as non-inner classes?
225  * <li> Document handling of cgi stdin when there is no stdin
226  * <li> Revisit IOException handling in CGIRunner.run()
227  * <li> Better documentation
228  * <li> Confirm use of ServletInputStream.available() in CGIRunner.run() is
229  * not needed
230  * <li> Make checking for "." and ".." in servlet & cgi PATH_INFO less
231  * draconian
232  * <li> [add more to this TODO list]
233  * </ul>
234  * </p>
235  *
236  * @author Martin T Dengler [root@martindengler.com]
237  * @author Amy Roh
238  * @version $Revision: 471267 $, $Date: 2006-11-04 22:40:59 +0100 (sam., 04 nov. 2006) $
239  * @since Tomcat 4.0
240  *
241  */

242
243
244 public final class CGIServlet extends HttpServlet JavaDoc {
245
246     /* some vars below copied from Craig R. McClanahan's InvokerServlet */
247
248     /** the debugging detail level for this servlet. */
249     private int debug = 0;
250
251     /**
252      * The CGI search path will start at
253      * webAppRootDir + File.separator + cgiPathPrefix
254      * (or webAppRootDir alone if cgiPathPrefix is
255      * null)
256      */

257     private String JavaDoc cgiPathPrefix = null;
258
259     /** the executable to use with the script */
260     private String JavaDoc cgiExecutable = "perl";
261     
262     /** the encoding to use for parameters */
263     private String JavaDoc parameterEncoding = System.getProperty("file.encoding",
264                                                           "UTF-8");
265
266     /** object used to ensure multiple threads don't try to expand same file */
267     static Object JavaDoc expandFileLock = new Object JavaDoc();
268
269     /** the shell environment variables to be passed to the CGI script */
270     static Hashtable JavaDoc shellEnv = new Hashtable JavaDoc();
271
272     /**
273      * Sets instance variables.
274      * <P>
275      * Modified from Craig R. McClanahan's InvokerServlet
276      * </P>
277      *
278      * @param config a <code>ServletConfig</code> object
279      * containing the servlet's
280      * configuration and initialization
281      * parameters
282      *
283      * @exception ServletException if an exception has occurred that
284      * interferes with the servlet's normal
285      * operation
286      */

287     public void init(ServletConfig JavaDoc config) throws ServletException JavaDoc {
288
289         super.init(config);
290
291         // Verify that we were not accessed using the invoker servlet
292
String JavaDoc servletName = getServletConfig().getServletName();
293         if (servletName == null)
294             servletName = "";
295         if (servletName.startsWith("org.apache.catalina.INVOKER."))
296             throw new UnavailableException JavaDoc
297                 ("Cannot invoke CGIServlet through the invoker");
298         
299         // Set our properties from the initialization parameters
300
if (getServletConfig().getInitParameter("debug") != null)
301             debug = Integer.parseInt(getServletConfig().getInitParameter("debug"));
302         cgiPathPrefix = getServletConfig().getInitParameter("cgiPathPrefix");
303         boolean passShellEnvironment =
304             Boolean.valueOf(getServletConfig().getInitParameter("passShellEnvironment")).booleanValue();
305
306         if (passShellEnvironment) {
307             try {
308                 shellEnv.putAll(getShellEnvironment());
309             } catch (IOException JavaDoc ioe) {
310                 ServletException JavaDoc e = new ServletException JavaDoc(
311                         "Unable to read shell environment variables", ioe);
312                 throw e;
313             }
314         }
315
316         if (getServletConfig().getInitParameter("executable") != null) {
317             cgiExecutable = getServletConfig().getInitParameter("executable");
318         }
319
320         if (getServletConfig().getInitParameter("parameterEncoding") != null) {
321             parameterEncoding = getServletConfig().getInitParameter("parameterEncoding");
322         }
323
324     }
325
326
327
328     /**
329      * Prints out important Servlet API and container information
330      *
331      * <p>
332      * Copied from SnoopAllServlet by Craig R. McClanahan
333      * </p>
334      *
335      * @param out ServletOutputStream as target of the information
336      * @param req HttpServletRequest object used as source of information
337      * @param res HttpServletResponse object currently not used but could
338      * provide future information
339      *
340      * @exception IOException if a write operation exception occurs
341      *
342      */

343     protected void printServletEnvironment(ServletOutputStream JavaDoc out,
344         HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res) throws IOException JavaDoc {
345
346         // Document the properties from ServletRequest
347
out.println("<h1>ServletRequest Properties</h1>");
348         out.println("<ul>");
349         Enumeration JavaDoc attrs = req.getAttributeNames();
350         while (attrs.hasMoreElements()) {
351             String JavaDoc attr = (String JavaDoc) attrs.nextElement();
352             out.println("<li><b>attribute</b> " + attr + " = " +
353                            req.getAttribute(attr));
354         }
355         out.println("<li><b>characterEncoding</b> = " +
356                        req.getCharacterEncoding());
357         out.println("<li><b>contentLength</b> = " +
358                        req.getContentLength());
359         out.println("<li><b>contentType</b> = " +
360                        req.getContentType());
361         Enumeration JavaDoc locales = req.getLocales();
362         while (locales.hasMoreElements()) {
363             Locale JavaDoc locale = (Locale JavaDoc) locales.nextElement();
364             out.println("<li><b>locale</b> = " + locale);
365         }
366         Enumeration JavaDoc params = req.getParameterNames();
367         while (params.hasMoreElements()) {
368             String JavaDoc param = (String JavaDoc) params.nextElement();
369             String JavaDoc values[] = req.getParameterValues(param);
370             for (int i = 0; i < values.length; i++)
371                 out.println("<li><b>parameter</b> " + param + " = " +
372                                values[i]);
373         }
374         out.println("<li><b>protocol</b> = " + req.getProtocol());
375         out.println("<li><b>remoteAddr</b> = " + req.getRemoteAddr());
376         out.println("<li><b>remoteHost</b> = " + req.getRemoteHost());
377         out.println("<li><b>scheme</b> = " + req.getScheme());
378         out.println("<li><b>secure</b> = " + req.isSecure());
379         out.println("<li><b>serverName</b> = " + req.getServerName());
380         out.println("<li><b>serverPort</b> = " + req.getServerPort());
381         out.println("</ul>");
382         out.println("<hr>");
383
384         // Document the properties from HttpServletRequest
385
out.println("<h1>HttpServletRequest Properties</h1>");
386         out.println("<ul>");
387         out.println("<li><b>authType</b> = " + req.getAuthType());
388         out.println("<li><b>contextPath</b> = " +
389                        req.getContextPath());
390         Cookie JavaDoc cookies[] = req.getCookies();
391         if (cookies!=null) {
392             for (int i = 0; i < cookies.length; i++)
393                 out.println("<li><b>cookie</b> " + cookies[i].getName() +" = " +cookies[i].getValue());
394         }
395         Enumeration JavaDoc headers = req.getHeaderNames();
396         while (headers.hasMoreElements()) {
397             String JavaDoc header = (String JavaDoc) headers.nextElement();
398             out.println("<li><b>header</b> " + header + " = " +
399                            req.getHeader(header));
400         }
401         out.println("<li><b>method</b> = " + req.getMethod());
402         out.println("<li><a name=\"pathInfo\"><b>pathInfo</b></a> = "
403                     + req.getPathInfo());
404         out.println("<li><b>pathTranslated</b> = " +
405                        req.getPathTranslated());
406         out.println("<li><b>queryString</b> = " +
407                        req.getQueryString());
408         out.println("<li><b>remoteUser</b> = " +
409                        req.getRemoteUser());
410         out.println("<li><b>requestedSessionId</b> = " +
411                        req.getRequestedSessionId());
412         out.println("<li><b>requestedSessionIdFromCookie</b> = " +
413                        req.isRequestedSessionIdFromCookie());
414         out.println("<li><b>requestedSessionIdFromURL</b> = " +
415                        req.isRequestedSessionIdFromURL());
416         out.println("<li><b>requestedSessionIdValid</b> = " +
417                        req.isRequestedSessionIdValid());
418         out.println("<li><b>requestURI</b> = " +
419                        req.getRequestURI());
420         out.println("<li><b>servletPath</b> = " +
421                        req.getServletPath());
422         out.println("<li><b>userPrincipal</b> = " +
423                        req.getUserPrincipal());
424         out.println("</ul>");
425         out.println("<hr>");
426
427         // Document the servlet request attributes
428
out.println("<h1>ServletRequest Attributes</h1>");
429         out.println("<ul>");
430         attrs = req.getAttributeNames();
431         while (attrs.hasMoreElements()) {
432             String JavaDoc attr = (String JavaDoc) attrs.nextElement();
433             out.println("<li><b>" + attr + "</b> = " +
434                            req.getAttribute(attr));
435         }
436         out.println("</ul>");
437         out.println("<hr>");
438
439         // Process the current session (if there is one)
440
HttpSession JavaDoc session = req.getSession(false);
441         if (session != null) {
442
443             // Document the session properties
444
out.println("<h1>HttpSession Properties</h1>");
445             out.println("<ul>");
446             out.println("<li><b>id</b> = " +
447                            session.getId());
448             out.println("<li><b>creationTime</b> = " +
449                            new Date JavaDoc(session.getCreationTime()));
450             out.println("<li><b>lastAccessedTime</b> = " +
451                            new Date JavaDoc(session.getLastAccessedTime()));
452             out.println("<li><b>maxInactiveInterval</b> = " +
453                            session.getMaxInactiveInterval());
454             out.println("</ul>");
455             out.println("<hr>");
456
457             // Document the session attributes
458
out.println("<h1>HttpSession Attributes</h1>");
459             out.println("<ul>");
460             attrs = session.getAttributeNames();
461             while (attrs.hasMoreElements()) {
462                 String JavaDoc attr = (String JavaDoc) attrs.nextElement();
463                 out.println("<li><b>" + attr + "</b> = " +
464                                session.getAttribute(attr));
465             }
466             out.println("</ul>");
467             out.println("<hr>");
468
469         }
470
471         // Document the servlet configuration properties
472
out.println("<h1>ServletConfig Properties</h1>");
473         out.println("<ul>");
474         out.println("<li><b>servletName</b> = " +
475                        getServletConfig().getServletName());
476         out.println("</ul>");
477         out.println("<hr>");
478
479         // Document the servlet configuration initialization parameters
480
out.println("<h1>ServletConfig Initialization Parameters</h1>");
481         out.println("<ul>");
482         params = getServletConfig().getInitParameterNames();
483         while (params.hasMoreElements()) {
484             String JavaDoc param = (String JavaDoc) params.nextElement();
485             String JavaDoc value = getServletConfig().getInitParameter(param);
486             out.println("<li><b>" + param + "</b> = " + value);
487         }
488         out.println("</ul>");
489         out.println("<hr>");
490
491         // Document the servlet context properties
492
out.println("<h1>ServletContext Properties</h1>");
493         out.println("<ul>");
494         out.println("<li><b>majorVersion</b> = " +
495                        getServletContext().getMajorVersion());
496         out.println("<li><b>minorVersion</b> = " +
497                        getServletContext().getMinorVersion());
498         out.println("<li><b>realPath('/')</b> = " +
499                        getServletContext().getRealPath("/"));
500         out.println("<li><b>serverInfo</b> = " +
501                        getServletContext().getServerInfo());
502         out.println("</ul>");
503         out.println("<hr>");
504
505         // Document the servlet context initialization parameters
506
out.println("<h1>ServletContext Initialization Parameters</h1>");
507         out.println("<ul>");
508         params = getServletContext().getInitParameterNames();
509         while (params.hasMoreElements()) {
510             String JavaDoc param = (String JavaDoc) params.nextElement();
511             String JavaDoc value = getServletContext().getInitParameter(param);
512             out.println("<li><b>" + param + "</b> = " + value);
513         }
514         out.println("</ul>");
515         out.println("<hr>");
516
517         // Document the servlet context attributes
518
out.println("<h1>ServletContext Attributes</h1>");
519         out.println("<ul>");
520         attrs = getServletContext().getAttributeNames();
521         while (attrs.hasMoreElements()) {
522             String JavaDoc attr = (String JavaDoc) attrs.nextElement();
523             out.println("<li><b>" + attr + "</b> = " +
524                            getServletContext().getAttribute(attr));
525         }
526         out.println("</ul>");
527         out.println("<hr>");
528
529
530
531     }
532
533
534
535     /**
536      * Provides CGI Gateway service -- delegates to <code>doGet</code>
537      *
538      * @param req HttpServletRequest passed in by servlet container
539      * @param res HttpServletResponse passed in by servlet container
540      *
541      * @exception ServletException if a servlet-specific exception occurs
542      * @exception IOException if a read/write exception occurs
543      *
544      * @see javax.servlet.http.HttpServlet
545      *
546      */

547     protected void doPost(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res)
548         throws IOException JavaDoc, ServletException JavaDoc {
549         doGet(req, res);
550     }
551
552
553
554     /**
555      * Provides CGI Gateway service
556      *
557      * @param req HttpServletRequest passed in by servlet container
558      * @param res HttpServletResponse passed in by servlet container
559      *
560      * @exception ServletException if a servlet-specific exception occurs
561      * @exception IOException if a read/write exception occurs
562      *
563      * @see javax.servlet.http.HttpServlet
564      *
565      */

566     protected void doGet(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc res)
567         throws ServletException JavaDoc, IOException JavaDoc {
568
569         // Verify that we were not accessed using the invoker servlet
570
if (req.getAttribute(Globals.INVOKED_ATTR) != null)
571             throw new UnavailableException JavaDoc
572                 ("Cannot invoke CGIServlet through the invoker");
573
574         CGIEnvironment cgiEnv = new CGIEnvironment(req, getServletContext());
575
576         if (cgiEnv.isValid()) {
577             CGIRunner cgi = new CGIRunner(cgiEnv.getCommand(),
578                                           cgiEnv.getEnvironment(),
579                                           cgiEnv.getWorkingDirectory(),
580                                           cgiEnv.getParameters());
581             //if POST, we need to cgi.setInput
582
//REMIND: how does this interact with Servlet API 2.3's Filters?!
583
if ("POST".equals(req.getMethod())) {
584                 cgi.setInput(req.getInputStream());
585             }
586             cgi.setResponse(res);
587             cgi.run();
588         }
589
590         if (!cgiEnv.isValid()) {
591             res.setStatus(404);
592         }
593  
594         if (debug >= 10) {
595
596             ServletOutputStream JavaDoc out = res.getOutputStream();
597             out.println("<HTML><HEAD><TITLE>$Name$</TITLE></HEAD>");
598             out.println("<BODY>$Header$<p>");
599
600             if (cgiEnv.isValid()) {
601                 out.println(cgiEnv.toString());
602             } else {
603                 out.println("<H3>");
604                 out.println("CGI script not found or not specified.");
605                 out.println("</H3>");
606                 out.println("<H4>");
607                 out.println("Check the <b>HttpServletRequest ");
608                 out.println("<a HREF=\"#pathInfo\">pathInfo</a></b> ");
609                 out.println("property to see if it is what you meant ");
610                 out.println("it to be. You must specify an existant ");
611                 out.println("and executable file as part of the ");
612                 out.println("path-info.");
613                 out.println("</H4>");
614                 out.println("<H4>");
615                 out.println("For a good discussion of how CGI scripts ");
616                 out.println("work and what their environment variables ");
617                 out.println("mean, please visit the <a ");
618                 out.println("href=\"http://cgi-spec.golux.com\">CGI ");
619                 out.println("Specification page</a>.");
620                 out.println("</H4>");
621
622             }
623
624             printServletEnvironment(out, req, res);
625
626             out.println("</BODY></HTML>");
627
628         }
629
630
631     } //doGet
632

633
634
635     /** For future testing use only; does nothing right now */
636     public static void main(String JavaDoc[] args) {
637         System.out.println("$Header$");
638     }
639
640     /**
641      * Get all shell environment variables. Have to do it this rather ugly way
642      * as the API to obtain is not available in 1.4 and earlier APIs.
643      *
644      * See <a HREF="http://www.rgagnon.com/javadetails/java-0150.html">Read environment
645      * variables from an application</a> for original source and article.
646      */

647     private Hashtable JavaDoc getShellEnvironment() throws IOException JavaDoc {
648         Hashtable JavaDoc envVars = new Hashtable JavaDoc();
649         Process JavaDoc p = null;
650         Runtime JavaDoc r = Runtime.getRuntime();
651         String JavaDoc OS = System.getProperty("os.name").toLowerCase();
652         boolean ignoreCase;
653
654         if (OS.indexOf("windows 9") > -1) {
655             p = r.exec( "command.com /c set" );
656             ignoreCase = true;
657         } else if ( (OS.indexOf("nt") > -1)
658                 || (OS.indexOf("windows 20") > -1)
659                 || (OS.indexOf("windows xp") > -1) ) {
660             // thanks to JuanFran for the xp fix!
661
p = r.exec( "cmd.exe /c set" );
662             ignoreCase = true;
663         } else {
664             // our last hope, we assume Unix (thanks to H. Ware for the fix)
665
p = r.exec( "env" );
666             ignoreCase = false;
667         }
668
669         BufferedReader JavaDoc br = new BufferedReader JavaDoc
670             ( new InputStreamReader JavaDoc( p.getInputStream() ) );
671         String JavaDoc line;
672         while( (line = br.readLine()) != null ) {
673             int idx = line.indexOf( '=' );
674             String JavaDoc key = line.substring( 0, idx );
675             String JavaDoc value = line.substring( idx+1 );
676             if (ignoreCase) {
677                 key = key.toUpperCase();
678             }
679             envVars.put(key, value);
680         }
681         return envVars;
682     }
683
684
685
686
687
688
689
690     /**
691      * Encapsulates the CGI environment and rules to derive
692      * that environment from the servlet container and request information.
693      *
694      * <p>
695      * </p>
696      *
697      * @version $Revision: 471267 $, $Date: 2006-11-04 22:40:59 +0100 (sam., 04 nov. 2006) $
698      * @since Tomcat 4.0
699      *
700      */

701     protected class CGIEnvironment {
702
703
704         /** context of the enclosing servlet */
705         private ServletContext JavaDoc context = null;
706
707         /** context path of enclosing servlet */
708         private String JavaDoc contextPath = null;
709
710         /** servlet URI of the enclosing servlet */
711         private String JavaDoc servletPath = null;
712
713         /** pathInfo for the current request */
714         private String JavaDoc pathInfo = null;
715
716         /** real file system directory of the enclosing servlet's web app */
717         private String JavaDoc webAppRootDir = null;
718
719         /** tempdir for context - used to expand scripts in unexpanded wars */
720         private File JavaDoc tmpDir = null;
721
722         /** derived cgi environment */
723         private Hashtable JavaDoc env = null;
724
725         /** cgi command to be invoked */
726         private String JavaDoc command = null;
727
728         /** cgi command's desired working directory */
729         private File JavaDoc workingDirectory = null;
730
731         /** cgi command's command line parameters */
732         private ArrayList JavaDoc cmdLineParameters = new ArrayList JavaDoc();
733
734         /** whether or not this object is valid or not */
735         private boolean valid = false;
736
737
738         /**
739          * Creates a CGIEnvironment and derives the necessary environment,
740          * query parameters, working directory, cgi command, etc.
741          *
742          * @param req HttpServletRequest for information provided by
743          * the Servlet API
744          * @param context ServletContext for information provided by the
745          * Servlet API
746          *
747          */

748         protected CGIEnvironment(HttpServletRequest JavaDoc req,
749                                  ServletContext JavaDoc context) throws IOException JavaDoc {
750             setupFromContext(context);
751             setupFromRequest(req);
752
753             this.valid = setCGIEnvironment(req);
754
755             if (this.valid) {
756                 workingDirectory = new File JavaDoc(command.substring(0,
757                       command.lastIndexOf(File.separator)));
758             }
759
760         }
761
762
763
764         /**
765          * Uses the ServletContext to set some CGI variables
766          *
767          * @param context ServletContext for information provided by the
768          * Servlet API
769          */

770         protected void setupFromContext(ServletContext JavaDoc context) {
771             this.context = context;
772             this.webAppRootDir = context.getRealPath("/");
773             this.tmpDir = (File JavaDoc) context.getAttribute(Globals.WORK_DIR_ATTR);
774         }
775
776
777
778         /**
779          * Uses the HttpServletRequest to set most CGI variables
780          *
781          * @param req HttpServletRequest for information provided by
782          * the Servlet API
783          * @throws UnsupportedEncodingException
784          */

785         protected void setupFromRequest(HttpServletRequest JavaDoc req)
786                 throws UnsupportedEncodingException JavaDoc {
787             
788             this.contextPath = req.getContextPath();
789             this.servletPath = req.getServletPath();
790             this.pathInfo = req.getPathInfo();
791             // If getPathInfo() returns null, must be using extension mapping
792
// In this case, pathInfo should be same as servletPath
793
if (this.pathInfo == null) {
794                 this.pathInfo = this.servletPath;
795             }
796             
797             // If request is HEAD or GET and Query String does not contain
798
// an unencoded "=" this is an indexed query. Parsed Query String
799
// forms command line parameters for cgi command.
800
if (!"GET".equals(req.getMethod()) &&
801                     !"HEAD".equals(req.getMethod()))
802                 return;
803             
804             String JavaDoc qs = req.getQueryString();
805             
806             if (qs == null || qs.indexOf("=")>0)
807                 return;
808             
809             int delimIndex = 0;
810             int lastDelimIndex = 0;
811             delimIndex = qs.indexOf("+");
812             
813             while (delimIndex >0) {
814                 cmdLineParameters.add(URLDecoder.decode(qs.substring(
815                         lastDelimIndex,delimIndex),parameterEncoding));
816                 lastDelimIndex = delimIndex + 1;
817                 delimIndex = qs.indexOf("+",lastDelimIndex);
818             }
819             cmdLineParameters.add(URLDecoder.decode(qs.substring(
820                     lastDelimIndex),parameterEncoding));
821         }
822
823
824         /**
825          * Resolves core information about the cgi script.
826          *
827          * <p>
828          * Example URI:
829          * <PRE> /servlet/cgigateway/dir1/realCGIscript/pathinfo1 </PRE>
830          * <ul>
831          * <LI><b>path</b> = $CATALINA_HOME/mywebapp/dir1/realCGIscript
832          * <LI><b>scriptName</b> = /servlet/cgigateway/dir1/realCGIscript
833          * <LI><b>cgiName</b> = /dir1/realCGIscript
834          * <LI><b>name</b> = realCGIscript
835          * </ul>
836          * </p>
837          * <p>
838          * CGI search algorithm: search the real path below
839          * &lt;my-webapp-root&gt; and find the first non-directory in
840          * the getPathTranslated("/"), reading/searching from left-to-right.
841          *</p>
842          *<p>
843          * The CGI search path will start at
844          * webAppRootDir + File.separator + cgiPathPrefix
845          * (or webAppRootDir alone if cgiPathPrefix is
846          * null).
847          *</p>
848          *<p>
849          * cgiPathPrefix is defined by setting
850          * this servlet's cgiPathPrefix init parameter
851          *
852          *</p>
853          *
854          * @param pathInfo String from HttpServletRequest.getPathInfo()
855          * @param webAppRootDir String from context.getRealPath("/")
856          * @param contextPath String as from
857          * HttpServletRequest.getContextPath()
858          * @param servletPath String as from
859          * HttpServletRequest.getServletPath()
860          * @param cgiPathPrefix subdirectory of webAppRootDir below which
861          * the web app's CGIs may be stored; can be null.
862          * The CGI search path will start at
863          * webAppRootDir + File.separator + cgiPathPrefix
864          * (or webAppRootDir alone if cgiPathPrefix is
865          * null). cgiPathPrefix is defined by setting
866          * the servlet's cgiPathPrefix init parameter.
867          *
868          *
869          * @return
870          * <ul>
871          * <li>
872          * <code>path</code> - full file-system path to valid cgi script,
873          * or null if no cgi was found
874          * <li>
875          * <code>scriptName</code> -
876          * CGI variable SCRIPT_NAME; the full URL path
877          * to valid cgi script or null if no cgi was
878          * found
879          * <li>
880          * <code>cgiName</code> - servlet pathInfo fragment corresponding to
881          * the cgi script itself, or null if not found
882          * <li>
883          * <code>name</code> - simple name (no directories) of the
884          * cgi script, or null if no cgi was found
885          * </ul>
886          *
887          * @since Tomcat 4.0
888          */

889         protected String JavaDoc[] findCGI(String JavaDoc pathInfo, String JavaDoc webAppRootDir,
890                                    String JavaDoc contextPath, String JavaDoc servletPath,
891                                    String JavaDoc cgiPathPrefix) {
892             String JavaDoc path = null;
893             String JavaDoc name = null;
894             String JavaDoc scriptname = null;
895             String JavaDoc cginame = null;
896
897             if ((webAppRootDir != null)
898                 && (webAppRootDir.lastIndexOf(File.separator) ==
899                     (webAppRootDir.length() - 1))) {
900                     //strip the trailing "/" from the webAppRootDir
901
webAppRootDir =
902                     webAppRootDir.substring(0, (webAppRootDir.length() - 1));
903             }
904
905             if (cgiPathPrefix != null) {
906                 webAppRootDir = webAppRootDir + File.separator
907                     + cgiPathPrefix;
908             }
909
910             if (debug >= 2) {
911                 log("findCGI: path=" + pathInfo + ", " + webAppRootDir);
912             }
913
914             File JavaDoc currentLocation = new File JavaDoc(webAppRootDir);
915             StringTokenizer JavaDoc dirWalker =
916             new StringTokenizer JavaDoc(pathInfo, "/");
917             if (debug >= 3) {
918                 log("findCGI: currentLoc=" + currentLocation);
919             }
920             while (!currentLocation.isFile() && dirWalker.hasMoreElements()) {
921                 if (debug >= 3) {
922                     log("findCGI: currentLoc=" + currentLocation);
923                 }
924                 currentLocation = new File JavaDoc(currentLocation,
925                                            (String JavaDoc) dirWalker.nextElement());
926             }
927             if (!currentLocation.isFile()) {
928                 return new String JavaDoc[] { null, null, null, null };
929             } else {
930                 if (debug >= 2) {
931                     log("findCGI: FOUND cgi at " + currentLocation);
932                 }
933                 path = currentLocation.getAbsolutePath();
934                 name = currentLocation.getName();
935                 cginame = (currentLocation.getParent() + File.separator).
936                         substring(webAppRootDir.length()) + name;
937
938                 if (".".equals(contextPath)) {
939                     scriptname = servletPath + cginame;
940                 } else {
941                     scriptname = contextPath + servletPath + cginame;
942                 }
943             }
944
945             if (debug >= 1) {
946                 log("findCGI calc: name=" + name + ", path=" + path
947                     + ", scriptname=" + scriptname + ", cginame=" + cginame);
948             }
949             return new String JavaDoc[] { path, scriptname, cginame, name };
950         }
951
952         /**
953          * Constructs the CGI environment to be supplied to the invoked CGI
954          * script; relies heavliy on Servlet API methods and findCGI
955          *
956          * @param req request associated with the CGI
957          * invokation
958          *
959          * @return true if environment was set OK, false if there
960          * was a problem and no environment was set
961          */

962         protected boolean setCGIEnvironment(HttpServletRequest JavaDoc req) throws IOException JavaDoc {
963
964             /*
965              * This method is slightly ugly; c'est la vie.
966              * "You cannot stop [ugliness], you can only hope to contain [it]"
967              * (apologies to Marv Albert regarding MJ)
968              */

969
970             Hashtable JavaDoc envp = new Hashtable JavaDoc();
971
972             // Add the shell environment variables (if any)
973
envp.putAll(shellEnv);
974
975             // Add the CGI environment variables
976
String JavaDoc sPathInfoOrig = null;
977             String JavaDoc sPathTranslatedOrig = null;
978             String JavaDoc sPathInfoCGI = null;
979             String JavaDoc sPathTranslatedCGI = null;
980             String JavaDoc sCGIFullPath = null;
981             String JavaDoc sCGIScriptName = null;
982             String JavaDoc sCGIFullName = null;
983             String JavaDoc sCGIName = null;
984             String JavaDoc[] sCGINames;
985
986
987             sPathInfoOrig = this.pathInfo;
988             sPathInfoOrig = sPathInfoOrig == null ? "" : sPathInfoOrig;
989
990             sPathTranslatedOrig = req.getPathTranslated();
991             sPathTranslatedOrig =
992                 sPathTranslatedOrig == null ? "" : sPathTranslatedOrig;
993
994             if (webAppRootDir == null ) {
995                 // The app has not been deployed in exploded form
996
webAppRootDir = tmpDir.toString();
997                 expandCGIScript();
998             }
999             
1000            sCGINames = findCGI(sPathInfoOrig,
1001                                webAppRootDir,
1002                                contextPath,
1003                                servletPath,
1004                                cgiPathPrefix);
1005
1006            sCGIFullPath = sCGINames[0];
1007            sCGIScriptName = sCGINames[1];
1008            sCGIFullName = sCGINames[2];
1009            sCGIName = sCGINames[3];
1010
1011            if (sCGIFullPath == null
1012                || sCGIScriptName == null
1013                || sCGIFullName == null
1014                || sCGIName == null) {
1015                return false;
1016            }
1017
1018            envp.put("SERVER_SOFTWARE", "TOMCAT");
1019
1020            envp.put("SERVER_NAME", nullsToBlanks(req.getServerName()));
1021
1022            envp.put("GATEWAY_INTERFACE", "CGI/1.1");
1023
1024            envp.put("SERVER_PROTOCOL", nullsToBlanks(req.getProtocol()));
1025
1026            int port = req.getServerPort();
1027            Integer JavaDoc iPort = (port == 0 ? new Integer JavaDoc(-1) : new Integer JavaDoc(port));
1028            envp.put("SERVER_PORT", iPort.toString());
1029
1030            envp.put("REQUEST_METHOD", nullsToBlanks(req.getMethod()));
1031
1032            envp.put("REQUEST_URI", nullsToBlanks(req.getRequestURI()));
1033
1034
1035            /*-
1036             * PATH_INFO should be determined by using sCGIFullName:
1037             * 1) Let sCGIFullName not end in a "/" (see method findCGI)
1038             * 2) Let sCGIFullName equal the pathInfo fragment which
1039             * corresponds to the actual cgi script.
1040             * 3) Thus, PATH_INFO = request.getPathInfo().substring(
1041             * sCGIFullName.length())
1042             *
1043             * (see method findCGI, where the real work is done)
1044             *
1045             */

1046            if (pathInfo == null
1047                || (pathInfo.substring(sCGIFullName.length()).length() <= 0)) {
1048                sPathInfoCGI = "";
1049            } else {
1050                sPathInfoCGI = pathInfo.substring(sCGIFullName.length());
1051            }
1052            envp.put("PATH_INFO", sPathInfoCGI);
1053
1054
1055            /*-
1056             * PATH_TRANSLATED must be determined after PATH_INFO (and the
1057             * implied real cgi-script) has been taken into account.
1058             *
1059             * The following example demonstrates:
1060             *
1061             * servlet info = /servlet/cgigw/dir1/dir2/cgi1/trans1/trans2
1062             * cgifullpath = /servlet/cgigw/dir1/dir2/cgi1
1063             * path_info = /trans1/trans2
1064             * webAppRootDir = servletContext.getRealPath("/")
1065             *
1066             * path_translated = servletContext.getRealPath("/trans1/trans2")
1067             *
1068             * That is, PATH_TRANSLATED = webAppRootDir + sPathInfoCGI
1069             * (unless sPathInfoCGI is null or blank, then the CGI
1070             * specification dictates that the PATH_TRANSLATED metavariable
1071             * SHOULD NOT be defined.
1072             *
1073             */

1074            if (sPathInfoCGI != null && !("".equals(sPathInfoCGI))) {
1075                sPathTranslatedCGI = context.getRealPath(sPathInfoCGI);
1076            } else {
1077                sPathTranslatedCGI = null;
1078            }
1079            if (sPathTranslatedCGI == null || "".equals(sPathTranslatedCGI)) {
1080                //NOOP
1081
} else {
1082                envp.put("PATH_TRANSLATED", nullsToBlanks(sPathTranslatedCGI));
1083            }
1084
1085
1086            envp.put("SCRIPT_NAME", nullsToBlanks(sCGIScriptName));
1087
1088            envp.put("QUERY_STRING", nullsToBlanks(req.getQueryString()));
1089
1090            envp.put("REMOTE_HOST", nullsToBlanks(req.getRemoteHost()));
1091
1092            envp.put("REMOTE_ADDR", nullsToBlanks(req.getRemoteAddr()));
1093
1094            envp.put("AUTH_TYPE", nullsToBlanks(req.getAuthType()));
1095
1096            envp.put("REMOTE_USER", nullsToBlanks(req.getRemoteUser()));
1097
1098            envp.put("REMOTE_IDENT", ""); //not necessary for full compliance
1099

1100            envp.put("CONTENT_TYPE", nullsToBlanks(req.getContentType()));
1101
1102
1103            /* Note CGI spec says CONTENT_LENGTH must be NULL ("") or undefined
1104             * if there is no content, so we cannot put 0 or -1 in as per the
1105             * Servlet API spec.
1106             */

1107            int contentLength = req.getContentLength();
1108            String JavaDoc sContentLength = (contentLength <= 0 ? "" :
1109                                     (new Integer JavaDoc(contentLength)).toString());
1110            envp.put("CONTENT_LENGTH", sContentLength);
1111
1112
1113            Enumeration JavaDoc headers = req.getHeaderNames();
1114            String JavaDoc header = null;
1115            while (headers.hasMoreElements()) {
1116                header = null;
1117                header = ((String JavaDoc) headers.nextElement()).toUpperCase();
1118                //REMIND: rewrite multiple headers as if received as single
1119
//REMIND: change character set
1120
//REMIND: I forgot what the previous REMIND means
1121
if ("AUTHORIZATION".equalsIgnoreCase(header) ||
1122                    "PROXY_AUTHORIZATION".equalsIgnoreCase(header)) {
1123                    //NOOP per CGI specification section 11.2
1124
} else {
1125                    envp.put("HTTP_" + header.replace('-', '_'),
1126                             req.getHeader(header));
1127                }
1128            }
1129
1130            File JavaDoc fCGIFullPath = new File JavaDoc(sCGIFullPath);
1131            command = fCGIFullPath.getCanonicalPath();
1132
1133            envp.put("X_TOMCAT_SCRIPT_PATH", command); //for kicks
1134

1135            envp.put("SCRIPT_FILENAME", command); //for PHP
1136

1137            this.env = envp;
1138
1139            return true;
1140
1141        }
1142
1143        /**
1144         * Extracts requested resource from web app archive to context work
1145         * directory to enable CGI script to be executed.
1146         */

1147        protected void expandCGIScript() {
1148            StringBuffer JavaDoc srcPath = new StringBuffer JavaDoc();
1149            StringBuffer JavaDoc destPath = new StringBuffer JavaDoc();
1150            InputStream JavaDoc is = null;
1151
1152            // paths depend on mapping
1153
if (cgiPathPrefix == null ) {
1154                srcPath.append(pathInfo);
1155                is = context.getResourceAsStream(srcPath.toString());
1156                destPath.append(tmpDir);
1157                destPath.append(pathInfo);
1158            } else {
1159                // essentially same search algorithm as findCGI()
1160
srcPath.append(cgiPathPrefix);
1161                StringTokenizer JavaDoc pathWalker =
1162                        new StringTokenizer JavaDoc (pathInfo, "/");
1163                // start with first element
1164
while (pathWalker.hasMoreElements() && (is == null)) {
1165                    srcPath.append("/");
1166                    srcPath.append(pathWalker.nextElement());
1167                    is = context.getResourceAsStream(srcPath.toString());
1168                }
1169                destPath.append(tmpDir);
1170                destPath.append("/");
1171                destPath.append(srcPath);
1172            }
1173
1174            if (is == null) {
1175                // didn't find anything, give up now
1176
if (debug >= 2) {
1177                    log("expandCGIScript: source '" + srcPath + "' not found");
1178                }
1179                 return;
1180            }
1181
1182            File JavaDoc f = new File JavaDoc(destPath.toString());
1183            if (f.exists()) {
1184                // Don't need to expand if it already exists
1185
return;
1186            }
1187
1188            // create directories
1189
String JavaDoc dirPath = new String JavaDoc (destPath.toString().substring(
1190                    0,destPath.toString().lastIndexOf("/")));
1191            File JavaDoc dir = new File JavaDoc(dirPath);
1192            dir.mkdirs();
1193
1194            try {
1195                synchronized (expandFileLock) {
1196                    // make sure file doesn't exist
1197
if (f.exists()) {
1198                        return;
1199                    }
1200
1201                    // create file
1202
if (!f.createNewFile()) {
1203                        return;
1204                    }
1205                    FileOutputStream JavaDoc fos = new FileOutputStream JavaDoc(f);
1206
1207                    // copy data
1208
IOTools.flow(is, fos);
1209                    is.close();
1210                    fos.close();
1211                    if (debug >= 2) {
1212                        log("expandCGIScript: expanded '" + srcPath + "' to '" + destPath + "'");
1213                    }
1214                }
1215            } catch (IOException JavaDoc ioe) {
1216                // delete in case file is corrupted
1217
if (f.exists()) {
1218                    f.delete();
1219                }
1220            }
1221        }
1222
1223
1224        /**
1225         * Print important CGI environment information in a easy-to-read HTML
1226         * table
1227         *
1228         * @return HTML string containing CGI environment info
1229         *
1230         */

1231        public String JavaDoc toString() {
1232
1233            StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
1234
1235            sb.append("<TABLE border=2>");
1236
1237            sb.append("<tr><th colspan=2 bgcolor=grey>");
1238            sb.append("CGIEnvironment Info</th></tr>");
1239
1240            sb.append("<tr><td>Debug Level</td><td>");
1241            sb.append(debug);
1242            sb.append("</td></tr>");
1243
1244            sb.append("<tr><td>Validity:</td><td>");
1245            sb.append(isValid());
1246            sb.append("</td></tr>");
1247
1248            if (isValid()) {
1249                Enumeration JavaDoc envk = env.keys();
1250                while (envk.hasMoreElements()) {
1251                    String JavaDoc s = (String JavaDoc) envk.nextElement();
1252                    sb.append("<tr><td>");
1253                    sb.append(s);
1254                    sb.append("</td><td>");
1255                    sb.append(blanksToString((String JavaDoc) env.get(s),
1256                                             "[will be set to blank]"));
1257                    sb.append("</td></tr>");
1258                }
1259            }
1260
1261            sb.append("<tr><td colspan=2><HR></td></tr>");
1262
1263            sb.append("<tr><td>Derived Command</td><td>");
1264            sb.append(nullsToBlanks(command));
1265            sb.append("</td></tr>");
1266
1267            sb.append("<tr><td>Working Directory</td><td>");
1268            if (workingDirectory != null) {
1269                sb.append(workingDirectory.toString());
1270            }
1271            sb.append("</td></tr>");
1272
1273            sb.append("<tr><td>Command Line Params</td><td>");
1274            for (int i=0; i < cmdLineParameters.size(); i++) {
1275                String JavaDoc param = (String JavaDoc) cmdLineParameters.get(i);
1276                sb.append("<p>");
1277                sb.append(param);
1278                sb.append("</p>");
1279            }
1280            sb.append("</td></tr>");
1281
1282            sb.append("</TABLE><p>end.");
1283
1284            return sb.toString();
1285        }
1286
1287
1288
1289        /**
1290         * Gets derived command string
1291         *
1292         * @return command string
1293         *
1294         */

1295        protected String JavaDoc getCommand() {
1296            return command;
1297        }
1298
1299
1300
1301        /**
1302         * Gets derived CGI working directory
1303         *
1304         * @return working directory
1305         *
1306         */

1307        protected File JavaDoc getWorkingDirectory() {
1308            return workingDirectory;
1309        }
1310
1311
1312
1313        /**
1314         * Gets derived CGI environment
1315         *
1316         * @return CGI environment
1317         *
1318         */

1319        protected Hashtable JavaDoc getEnvironment() {
1320            return env;
1321        }
1322
1323
1324
1325        /**
1326         * Gets derived CGI query parameters
1327         *
1328         * @return CGI query parameters
1329         *
1330         */

1331        protected ArrayList JavaDoc getParameters() {
1332            return cmdLineParameters;
1333        }
1334
1335
1336
1337        /**
1338         * Gets validity status
1339         *
1340         * @return true if this environment is valid, false
1341         * otherwise
1342         *
1343         */

1344        protected boolean isValid() {
1345            return valid;
1346        }
1347
1348
1349
1350        /**
1351         * Converts null strings to blank strings ("")
1352         *
1353         * @param s string to be converted if necessary
1354         * @return a non-null string, either the original or the empty string
1355         * ("") if the original was <code>null</code>
1356         */

1357        protected String JavaDoc nullsToBlanks(String JavaDoc s) {
1358            return nullsToString(s, "");
1359        }
1360
1361
1362
1363        /**
1364         * Converts null strings to another string
1365         *
1366         * @param couldBeNull string to be converted if necessary
1367         * @param subForNulls string to return instead of a null string
1368         * @return a non-null string, either the original or the substitute
1369         * string if the original was <code>null</code>
1370         */

1371        protected String JavaDoc nullsToString(String JavaDoc couldBeNull,
1372                                       String JavaDoc subForNulls) {
1373            return (couldBeNull == null ? subForNulls : couldBeNull);
1374        }
1375
1376
1377
1378        /**
1379         * Converts blank strings to another string
1380         *
1381         * @param couldBeBlank string to be converted if necessary
1382         * @param subForBlanks string to return instead of a blank string
1383         * @return a non-null string, either the original or the substitute
1384         * string if the original was <code>null</code> or empty ("")
1385         */

1386        protected String JavaDoc blanksToString(String JavaDoc couldBeBlank,
1387                                      String JavaDoc subForBlanks) {
1388            return (("".equals(couldBeBlank) || couldBeBlank == null)
1389                    ? subForBlanks
1390                    : couldBeBlank);
1391        }
1392
1393
1394
1395    } //class CGIEnvironment
1396

1397
1398
1399
1400
1401
1402    /**
1403     * Encapsulates the knowledge of how to run a CGI script, given the
1404     * script's desired environment and (optionally) input/output streams
1405     *
1406     * <p>
1407     *
1408     * Exposes a <code>run</code> method used to actually invoke the
1409     * CGI.
1410     *
1411     * </p>
1412     * <p>
1413     *
1414     * The CGI environment and settings are derived from the information
1415     * passed to the constuctor.
1416     *
1417     * </p>
1418     * <p>
1419     *
1420     * The input and output streams can be set by the <code>setInput</code>
1421     * and <code>setResponse</code> methods, respectively.
1422     * </p>
1423     *
1424     * @version $Revision: 471267 $, $Date: 2006-11-04 22:40:59 +0100 (sam., 04 nov. 2006) $
1425     */

1426
1427    protected class CGIRunner {
1428
1429        /** script/command to be executed */
1430        private String JavaDoc command = null;
1431
1432        /** environment used when invoking the cgi script */
1433        private Hashtable JavaDoc env = null;
1434
1435        /** working directory used when invoking the cgi script */
1436        private File JavaDoc wd = null;
1437
1438        /** command line parameters to be passed to the invoked script */
1439        private ArrayList JavaDoc params = null;
1440
1441        /** stdin to be passed to cgi script */
1442        private InputStream JavaDoc stdin = null;
1443
1444        /** response object used to set headers & get output stream */
1445        private HttpServletResponse JavaDoc response = null;
1446
1447        /** boolean tracking whether this object has enough info to run() */
1448        private boolean readyToRun = false;
1449
1450
1451
1452
1453        /**
1454         * Creates a CGIRunner and initializes its environment, working
1455         * directory, and query parameters.
1456         * <BR>
1457         * Input/output streams (optional) are set using the
1458         * <code>setInput</code> and <code>setResponse</code> methods,
1459         * respectively.
1460         *
1461         * @param command string full path to command to be executed
1462         * @param env Hashtable with the desired script environment
1463         * @param wd File with the script's desired working directory
1464         * @param params ArrayList with the script's query command line
1465         * paramters as strings
1466         */

1467        protected CGIRunner(String JavaDoc command, Hashtable JavaDoc env, File JavaDoc wd,
1468                            ArrayList JavaDoc params) {
1469            this.command = command;
1470            this.env = env;
1471            this.wd = wd;
1472            this.params = params;
1473            updateReadyStatus();
1474        }
1475
1476
1477
1478        /**
1479         * Checks & sets ready status
1480         */

1481        protected void updateReadyStatus() {
1482            if (command != null
1483                && env != null
1484                && wd != null
1485                && params != null
1486                && response != null) {
1487                readyToRun = true;
1488            } else {
1489                readyToRun = false;
1490            }
1491        }
1492
1493
1494
1495        /**
1496         * Gets ready status
1497         *
1498         * @return false if not ready (<code>run</code> will throw
1499         * an exception), true if ready
1500         */

1501        protected boolean isReady() {
1502            return readyToRun;
1503        }
1504
1505
1506
1507        /**
1508         * Sets HttpServletResponse object used to set headers and send
1509         * output to
1510         *
1511         * @param response HttpServletResponse to be used
1512         *
1513         */

1514        protected void setResponse(HttpServletResponse JavaDoc response) {
1515            this.response = response;
1516            updateReadyStatus();
1517        }
1518
1519
1520
1521        /**
1522         * Sets standard input to be passed on to the invoked cgi script
1523         *
1524         * @param stdin InputStream to be used
1525         *
1526         */

1527        protected void setInput(InputStream JavaDoc stdin) {
1528            this.stdin = stdin;
1529            updateReadyStatus();
1530        }
1531
1532
1533
1534        /**
1535         * Converts a Hashtable to a String array by converting each
1536         * key/value pair in the Hashtable to a String in the form
1537         * "key=value" (hashkey + "=" + hash.get(hashkey).toString())
1538         *
1539         * @param h Hashtable to convert
1540         *
1541         * @return converted string array
1542         *
1543         * @exception NullPointerException if a hash key has a null value
1544         *
1545         */

1546        protected String JavaDoc[] hashToStringArray(Hashtable JavaDoc h)
1547            throws NullPointerException JavaDoc {
1548            Vector JavaDoc v = new Vector JavaDoc();
1549            Enumeration JavaDoc e = h.keys();
1550            while (e.hasMoreElements()) {
1551                String JavaDoc k = e.nextElement().toString();
1552                v.add(k + "=" + h.get(k));
1553            }
1554            String JavaDoc[] strArr = new String JavaDoc[v.size()];
1555            v.copyInto(strArr);
1556            return strArr;
1557        }
1558
1559
1560
1561        /**
1562         * Executes a CGI script with the desired environment, current working
1563         * directory, and input/output streams
1564         *
1565         * <p>
1566         * This implements the following CGI specification recommedations:
1567         * <UL>
1568         * <LI> Servers SHOULD provide the "<code>query</code>" component of
1569         * the script-URI as command-line arguments to scripts if it
1570         * does not contain any unencoded "=" characters and the
1571         * command-line arguments can be generated in an unambiguous
1572         * manner.
1573         * <LI> Servers SHOULD set the AUTH_TYPE metavariable to the value
1574         * of the "<code>auth-scheme</code>" token of the
1575         * "<code>Authorization</code>" if it was supplied as part of the
1576         * request header. See <code>getCGIEnvironment</code> method.
1577         * <LI> Where applicable, servers SHOULD set the current working
1578         * directory to the directory in which the script is located
1579         * before invoking it.
1580         * <LI> Server implementations SHOULD define their behavior for the
1581         * following cases:
1582         * <ul>
1583         * <LI> <u>Allowed characters in pathInfo</u>: This implementation
1584         * does not allow ASCII NUL nor any character which cannot
1585         * be URL-encoded according to internet standards;
1586         * <LI> <u>Allowed characters in path segments</u>: This
1587         * implementation does not allow non-terminal NULL
1588         * segments in the the path -- IOExceptions may be thrown;
1589         * <LI> <u>"<code>.</code>" and "<code>..</code>" path
1590         * segments</u>:
1591         * This implementation does not allow "<code>.</code>" and
1592         * "<code>..</code>" in the the path, and such characters
1593         * will result in an IOException being thrown;
1594         * <LI> <u>Implementation limitations</u>: This implementation
1595         * does not impose any limitations except as documented
1596         * above. This implementation may be limited by the
1597         * servlet container used to house this implementation.
1598         * In particular, all the primary CGI variable values
1599         * are derived either directly or indirectly from the
1600         * container's implementation of the Servlet API methods.
1601         * </ul>
1602         * </UL>
1603         * </p>
1604         *
1605         * @exception IOException if problems during reading/writing occur
1606         *
1607         * @see java.lang.Runtime#exec(String command, String[] envp,
1608         * File dir)
1609         */

1610        protected void run() throws IOException JavaDoc {
1611
1612            /*
1613             * REMIND: this method feels too big; should it be re-written?
1614             */

1615
1616            if (!isReady()) {
1617                throw new IOException JavaDoc(this.getClass().getName()
1618                                      + ": not ready to run.");
1619            }
1620
1621            if (debug >= 1 ) {
1622                log("runCGI(envp=[" + env + "], command=" + command + ")");
1623            }
1624
1625            if ((command.indexOf(File.separator + "." + File.separator) >= 0)
1626                || (command.indexOf(File.separator + "..") >= 0)
1627                || (command.indexOf(".." + File.separator) >= 0)) {
1628                throw new IOException JavaDoc(this.getClass().getName()
1629                                      + "Illegal Character in CGI command "
1630                                      + "path ('.' or '..') detected. Not "
1631                                      + "running CGI [" + command + "].");
1632            }
1633
1634            /* original content/structure of this section taken from
1635             * http://developer.java.sun.com/developer/
1636             * bugParade/bugs/4216884.html
1637             * with major modifications by Martin Dengler
1638             */

1639            Runtime JavaDoc rt = null;
1640            InputStream JavaDoc cgiOutput = null;
1641            BufferedReader JavaDoc commandsStdErr = null;
1642            BufferedOutputStream JavaDoc commandsStdIn = null;
1643            Process JavaDoc proc = null;
1644            int bufRead = -1;
1645
1646            //create query arguments
1647
StringBuffer JavaDoc cmdAndArgs = new StringBuffer JavaDoc();
1648            if (command.indexOf(" ") < 0) {
1649                cmdAndArgs.append(command);
1650            } else {
1651                // Spaces used as delimiter, so need to use quotes
1652
cmdAndArgs.append("\"");
1653                cmdAndArgs.append(command);
1654                cmdAndArgs.append("\"");
1655            }
1656
1657            for (int i=0; i < params.size(); i++) {
1658                cmdAndArgs.append(" ");
1659                String JavaDoc param = (String JavaDoc) params.get(i);
1660                if (param.indexOf(" ") < 0) {
1661                    cmdAndArgs.append(param);
1662                } else {
1663                    // Spaces used as delimiter, so need to use quotes
1664
cmdAndArgs.append("\"");
1665                    cmdAndArgs.append(param);
1666                    cmdAndArgs.append("\"");
1667                }
1668            }
1669
1670            StringBuffer JavaDoc command = new StringBuffer JavaDoc(cgiExecutable);
1671            command.append(" ");
1672            command.append(cmdAndArgs.toString());
1673            cmdAndArgs = command;
1674
1675            try {
1676                rt = Runtime.getRuntime();
1677                proc = rt.exec(cmdAndArgs.toString(), hashToStringArray(env), wd);
1678    
1679                String JavaDoc sContentLength = (String JavaDoc) env.get("CONTENT_LENGTH");
1680
1681                if(!"".equals(sContentLength)) {
1682                    commandsStdIn = new BufferedOutputStream JavaDoc(proc.getOutputStream());
1683                    IOTools.flow(stdin, commandsStdIn);
1684                    commandsStdIn.flush();
1685                    commandsStdIn.close();
1686                }
1687
1688                /* we want to wait for the process to exit, Process.waitFor()
1689                 * is useless in our situation; see
1690                 * http://developer.java.sun.com/developer/
1691                 * bugParade/bugs/4223650.html
1692                 */

1693
1694                boolean isRunning = true;
1695                commandsStdErr = new BufferedReader JavaDoc
1696                    (new InputStreamReader JavaDoc(proc.getErrorStream()));
1697                final BufferedReader JavaDoc stdErrRdr = commandsStdErr ;
1698
1699                new Thread JavaDoc() {
1700                    public void run () {
1701                        sendToLog(stdErrRdr) ;
1702                    } ;
1703                }.start() ;
1704
1705                InputStream JavaDoc cgiHeaderStream =
1706                    new HTTPHeaderInputStream(proc.getInputStream());
1707                BufferedReader JavaDoc cgiHeaderReader =
1708                    new BufferedReader JavaDoc(new InputStreamReader JavaDoc(cgiHeaderStream));
1709            
1710                while (isRunning) {
1711                    try {
1712                        //set headers
1713
String JavaDoc line = null;
1714                        while (((line = cgiHeaderReader.readLine()) != null)
1715                               && !("".equals(line))) {
1716                            if (debug >= 2) {
1717                                log("runCGI: addHeader(\"" + line + "\")");
1718                            }
1719                            if (line.startsWith("HTTP")) {
1720                                response.setStatus(getSCFromHttpStatusLine(line));
1721                            } else if (line.indexOf(":") >= 0) {
1722                                String JavaDoc header =
1723                                    line.substring(0, line.indexOf(":")).trim();
1724                                String JavaDoc value =
1725                                    line.substring(line.indexOf(":") + 1).trim();
1726                                if (header.equalsIgnoreCase("status")) {
1727                                    response.setStatus(getSCFromCGIStatusHeader(value));
1728                                } else {
1729                                    response.addHeader(header , value);
1730                                }
1731                            } else {
1732                                log("runCGI: bad header line \"" + line + "\"");
1733                            }
1734                        }
1735    
1736                        //write output
1737
byte[] bBuf = new byte[2048];
1738    
1739                        OutputStream JavaDoc out = response.getOutputStream();
1740                        cgiOutput = proc.getInputStream();
1741    
1742                        try {
1743                            while ((bufRead = cgiOutput.read(bBuf)) != -1) {
1744                                if (debug >= 4) {
1745                                    log("runCGI: output " + bufRead +
1746                                        " bytes of data");
1747                                }
1748                                out.write(bBuf, 0, bufRead);
1749                            }
1750                        } finally {
1751                            // Attempt to consume any leftover byte if something bad happens,
1752
// such as a socket disconnect on the servlet side; otherwise, the
1753
// external process could hang
1754
if (bufRead != -1) {
1755                                while ((bufRead = cgiOutput.read(bBuf)) != -1) {}
1756                            }
1757                        }
1758        
1759                        proc.exitValue(); // Throws exception if alive
1760

1761                        isRunning = false;
1762    
1763                    } catch (IllegalThreadStateException JavaDoc e) {
1764                        try {
1765                            Thread.sleep(500);
1766                        } catch (InterruptedException JavaDoc ignored) {
1767                        }
1768                    }
1769                } //replacement for Process.waitFor()
1770

1771                // Close the output stream used
1772
cgiOutput.close();
1773            }
1774            catch (IOException JavaDoc e){
1775                log ("Caught exception " + e);
1776                throw e;
1777            }
1778            finally{
1779                if (debug > 4) {
1780                    log ("Running finally block");
1781                }
1782                if (proc != null){
1783                    proc.destroy();
1784                    proc = null;
1785                }
1786            }
1787        }
1788
1789        /**
1790         * Parses the Status-Line and extracts the status code.
1791         *
1792         * @param line The HTTP Status-Line (RFC2616, section 6.1)
1793         * @return The extracted status code or the code representing an
1794         * internal error if a valid status code cannot be extracted.
1795         */

1796        private int getSCFromHttpStatusLine(String JavaDoc line) {
1797            int statusStart = line.indexOf(' ') + 1;
1798            
1799            if (statusStart < 1 || line.length() < statusStart + 3) {
1800                // Not a valid HTTP Status-Line
1801
log ("runCGI: invalid HTTP Status-Line:" + line);
1802                return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
1803            }
1804            
1805            String JavaDoc status = line.substring(statusStart, statusStart + 3);
1806            
1807            int statusCode;
1808            try {
1809                statusCode = Integer.parseInt(status);
1810            } catch (NumberFormatException JavaDoc nfe) {
1811                // Not a valid status code
1812
log ("runCGI: invalid status code:" + status);
1813                return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
1814            }
1815            
1816            return statusCode;
1817        }
1818
1819        /**
1820         * Parses the CGI Status Header value and extracts the status code.
1821         *
1822         * @param value The CGI Status value of the form <code>
1823         * digit digit digit SP reason-phrase</code>
1824         * @return The extracted status code or the code representing an
1825         * internal error if a valid status code cannot be extracted.
1826         */

1827        private int getSCFromCGIStatusHeader(String JavaDoc value) {
1828            if (value.length() < 3) {
1829                // Not a valid status value
1830
log ("runCGI: invalid status value:" + value);
1831                return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
1832            }
1833            
1834            String JavaDoc status = value.substring(0, 3);
1835            
1836            int statusCode;
1837            try {
1838                statusCode = Integer.parseInt(status);
1839            } catch (NumberFormatException JavaDoc nfe) {
1840                // Not a valid status code
1841
log ("runCGI: invalid status code:" + status);
1842                return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
1843            }
1844            
1845            return statusCode;
1846        }
1847        
1848        private void sendToLog(BufferedReader JavaDoc rdr) {
1849            String JavaDoc line = null;
1850            int lineCount = 0 ;
1851            try {
1852                while ((line = rdr.readLine()) != null) {
1853                    log("runCGI (stderr):" + line) ;
1854                    lineCount++ ;
1855                }
1856            } catch (IOException JavaDoc e) {
1857                log("sendToLog error", e) ;
1858            } finally {
1859                try {
1860                    rdr.close() ;
1861                } catch (IOException JavaDoc ce) {
1862                    log("sendToLog error", ce) ;
1863                } ;
1864            } ;
1865            if ( lineCount > 0 && debug > 2) {
1866                log("runCGI: " + lineCount + " lines received on stderr") ;
1867            } ;
1868        }
1869    } //class CGIRunner
1870

1871    /**
1872     * This is an input stream specifically for reading HTTP headers. It reads
1873     * upto and including the two blank lines terminating the headers. It
1874     * allows the content to be read using bytes or characters as appropriate.
1875     */

1876    protected class HTTPHeaderInputStream extends InputStream JavaDoc {
1877        private static final int STATE_CHARACTER = 0;
1878        private static final int STATE_FIRST_CR = 1;
1879        private static final int STATE_FIRST_LF = 2;
1880        private static final int STATE_SECOND_CR = 3;
1881        private static final int STATE_HEADER_END = 4;
1882        
1883        private InputStream JavaDoc input;
1884        private int state;
1885        
1886        HTTPHeaderInputStream(InputStream JavaDoc theInput) {
1887            input = theInput;
1888            state = STATE_CHARACTER;
1889        }
1890
1891        /**
1892         * @see java.io.InputStream#read()
1893         */

1894        public int read() throws IOException JavaDoc {
1895            if (state == STATE_HEADER_END) {
1896                return -1;
1897            }
1898
1899            int i = input.read();
1900
1901            // Update the state
1902
// State machine looks like this
1903
//
1904
// -------->--------
1905
// | (CR) |
1906
// | |
1907
// CR1--->--- |
1908
// | | |
1909
// ^(CR) |(LF) |
1910
// | | |
1911
// CHAR--->--LF1--->--EOH
1912
// (LF) | (LF) |
1913
// |(CR) ^(LF)
1914
// | |
1915
// (CR2)-->---
1916

1917            if (i == 10) {
1918                // LF
1919
switch(state) {
1920                    case STATE_CHARACTER:
1921                        state = STATE_FIRST_LF;
1922                        break;
1923                    case STATE_FIRST_CR:
1924                        state = STATE_FIRST_LF;
1925                        break;
1926                    case STATE_FIRST_LF:
1927                    case STATE_SECOND_CR:
1928                        state = STATE_HEADER_END;
1929                        break;
1930                }
1931
1932            } else if (i == 13) {
1933                // CR
1934
switch(state) {
1935                    case STATE_CHARACTER:
1936                        state = STATE_FIRST_CR;
1937                        break;
1938                    case STATE_FIRST_CR:
1939                        state = STATE_HEADER_END;
1940                        break;
1941                    case STATE_FIRST_LF:
1942                        state = STATE_SECOND_CR;
1943                        break;
1944                }
1945
1946            } else {
1947                state = STATE_CHARACTER;
1948            }
1949            
1950            return i;
1951        }
1952    } // class HTTPHeaderInputStream
1953

1954} //class CGIServlet
1955
Popular Tags