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;
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.OutputStream;
29 import java.io.UnsupportedEncodingException;
30 import java.net.URLDecoder;
31 import java.util.ArrayList;
32 import java.util.Date;
33 import java.util.Enumeration;
34 import java.util.Hashtable;
35 import java.util.Locale;
36 import java.util.StringTokenizer;
37 import java.util.Vector;
38
39 import javax.servlet.ServletConfig;
40 import javax.servlet.ServletContext;
41 import javax.servlet.ServletException;
42 import javax.servlet.ServletOutputStream;
43 import javax.servlet.UnavailableException;
44 import javax.servlet.http.Cookie;
45 import javax.servlet.http.HttpServlet;
46 import javax.servlet.http.HttpServletRequest;
47 import javax.servlet.http.HttpServletResponse;
48 import javax.servlet.http.HttpSession;
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><web-app>/WEB-INF/web.xml</code>) to:
69 * </p>
70 * <p>
71 * <code>
72 * <web-app>/cgi-bin/*
73 * </code>
74 * </p>
75 * <p>
76 * then the following request:
77 * </p>
78 * <p>
79 * <code>
80 * http://localhost:8080/<web-app>/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 * <web-app-root>/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><webapp>/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: 595801 $, $Date: 2007-11-16 21:07:07 +0100 (ven., 16 nov. 2007) $
239 * @since Tomcat 4.0
240 *
241 */
242
243
244 public final class CGIServlet extends HttpServlet {
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 cgiPathPrefix = null;
258
259 /** the executable to use with the script */
260 private String cgiExecutable = "perl";
261
262 /** the encoding to use for parameters */
263 private String 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 expandFileLock = new Object();
268
269 /** the shell environment variables to be passed to the CGI script */
270 static Hashtable<String,String> shellEnv = new Hashtable<String,String>();
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 config) throws ServletException {
288
289 super.init(config);
290
291 // Verify that we were not accessed using the invoker servlet
292 String servletName = getServletConfig().getServletName();
293 if (servletName == null)
294 servletName = "";
295 if (servletName.startsWith("org.apache.catalina.INVOKER."))
296 throw new UnavailableException
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 ioe) {
310 ServletException e = new ServletException(
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 out,
344 HttpServletRequest req, HttpServletResponse res) throws IOException {
345
346 // Document the properties from ServletRequest
347 out.println("<h1>ServletRequest Properties</h1>");
348 out.println("<ul>");
349 Enumeration attrs = req.getAttributeNames();
350 while (attrs.hasMoreElements()) {
351 String attr = (String) 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 locales = req.getLocales();
362 while (locales.hasMoreElements()) {
363 Locale locale = (Locale) locales.nextElement();
364 out.println("<li><b>locale</b> = " + locale);
365 }
366 Enumeration params = req.getParameterNames();
367 while (params.hasMoreElements()) {
368 String param = (String) params.nextElement();
369 String 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 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 headers = req.getHeaderNames();
396 while (headers.hasMoreElements()) {
397 String header = (String) 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 attr = (String) 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 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(session.getCreationTime()));
450 out.println("<li><b>lastAccessedTime</b> = " +
451 new Date(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 attr = (String) 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 param = (String) params.nextElement();
485 String 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 param = (String) params.nextElement();
511 String 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 attr = (String) 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 req, HttpServletResponse res)
548 throws IOException, ServletException {
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 req, HttpServletResponse res)
567 throws ServletException, IOException {
568
569 // Verify that we were not accessed using the invoker servlet
570 if (req.getAttribute(Globals.INVOKED_ATTR) != null)
571 throw new UnavailableException
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 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[] 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<String,String> getShellEnvironment() throws IOException {
648 Hashtable<String,String> envVars = new Hashtable<String,String>();
649 Process p = null;
650 Runtime r = Runtime.getRuntime();
651 String 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 br = new BufferedReader
670 ( new InputStreamReader( p.getInputStream() ) );
671 String line;
672 while( (line = br.readLine()) != null ) {
673 int idx = line.indexOf( '=' );
674 String key = line.substring( 0, idx );
675 String 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: 595801 $, $Date: 2007-11-16 21:07:07 +0100 (ven., 16 nov. 2007) $
698 * @since Tomcat 4.0
699 *
700 */
701 protected class CGIEnvironment {
702
703
704 /** context of the enclosing servlet */
705 private ServletContext context = null;
706
707 /** context path of enclosing servlet */
708 private String contextPath = null;
709
710 /** servlet URI of the enclosing servlet */
711 private String servletPath = null;
712
713 /** pathInfo for the current request */
714 private String pathInfo = null;
715
716 /** real file system directory of the enclosing servlet's web app */
717 private String webAppRootDir = null;
718
719 /** tempdir for context - used to expand scripts in unexpanded wars */
720 private File tmpDir = null;
721
722 /** derived cgi environment */
723 private Hashtable env = null;
724
725 /** cgi command to be invoked */
726 private String command = null;
727
728 /** cgi command's desired working directory */
729 private File workingDirectory = null;
730
731 /** cgi command's command line parameters */
732 private ArrayList<String> cmdLineParameters = new ArrayList<String>();
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 req,
749 ServletContext context) throws IOException {
750 setupFromContext(context);
751 setupFromRequest(req);
752
753 this.valid = setCGIEnvironment(req);
754
755 if (this.valid) {
756 workingDirectory = new File(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 context) {
771 this.context = context;
772 this.webAppRootDir = context.getRealPath("/");
773 this.tmpDir = (File) 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 req)
786 throws UnsupportedEncodingException {
787
788 boolean isIncluded = false;
789
790 // Look to see if this request is an include
791 if (req.getAttribute(Globals.INCLUDE_REQUEST_URI_ATTR) != null) {
792 isIncluded = true;
793 }
794 if (isIncluded) {
795 this.contextPath = (String) req.getAttribute(
796 Globals.INCLUDE_CONTEXT_PATH_ATTR);
797 this.servletPath = (String) req.getAttribute(
798 Globals.INCLUDE_SERVLET_PATH_ATTR);
799 this.pathInfo = (String) req.getAttribute(
800 Globals.INCLUDE_PATH_INFO_ATTR);
801 } else {
802 this.contextPath = req.getContextPath();
803 this.servletPath = req.getServletPath();
804 this.pathInfo = req.getPathInfo();
805 }
806 // If getPathInfo() returns null, must be using extension mapping
807 // In this case, pathInfo should be same as servletPath
808 if (this.pathInfo == null) {
809 this.pathInfo = this.servletPath;
810 }
811
812 // If the request method is GET, POST or HEAD and the query string
813 // does not contain an unencoded "=" this is an indexed query.
814 // The parsed query string becomes the command line parameters
815 // for the cgi command.
816 if (req.getMethod().equals("GET")
817 || req.getMethod().equals("POST")
818 || req.getMethod().equals("HEAD")) {
819 String qs;
820 if (isIncluded) {
821 qs = (String) req.getAttribute(
822 Globals.INCLUDE_QUERY_STRING_ATTR);
823 } else {
824 qs = req.getQueryString();
825 }
826 if (qs != null && qs.indexOf("=") == -1) {
827 StringTokenizer qsTokens = new StringTokenizer(qs, "+");
828 while ( qsTokens.hasMoreTokens() ) {
829 cmdLineParameters.add(URLDecoder.decode(qsTokens.nextToken(),
830 parameterEncoding));
831 }
832 }
833 }
834 }
835
836
837 /**
838 * Resolves core information about the cgi script.
839 *
840 * <p>
841 * Example URI:
842 * <PRE> /servlet/cgigateway/dir1/realCGIscript/pathinfo1 </PRE>
843 * <ul>
844 * <LI><b>path</b> = $CATALINA_HOME/mywebapp/dir1/realCGIscript
845 * <LI><b>scriptName</b> = /servlet/cgigateway/dir1/realCGIscript
846 * <LI><b>cgiName</b> = /dir1/realCGIscript
847 * <LI><b>name</b> = realCGIscript
848 * </ul>
849 * </p>
850 * <p>
851 * CGI search algorithm: search the real path below
852 * <my-webapp-root> and find the first non-directory in
853 * the getPathTranslated("/"), reading/searching from left-to-right.
854 *</p>
855 *<p>
856 * The CGI search path will start at
857 * webAppRootDir + File.separator + cgiPathPrefix
858 * (or webAppRootDir alone if cgiPathPrefix is
859 * null).
860 *</p>
861 *<p>
862 * cgiPathPrefix is defined by setting
863 * this servlet's cgiPathPrefix init parameter
864 *
865 *</p>
866 *
867 * @param pathInfo String from HttpServletRequest.getPathInfo()
868 * @param webAppRootDir String from context.getRealPath("/")
869 * @param contextPath String as from
870 * HttpServletRequest.getContextPath()
871 * @param servletPath String as from
872 * HttpServletRequest.getServletPath()
873 * @param cgiPathPrefix subdirectory of webAppRootDir below which
874 * the web app's CGIs may be stored; can be null.
875 * The CGI search path will start at
876 * webAppRootDir + File.separator + cgiPathPrefix
877 * (or webAppRootDir alone if cgiPathPrefix is
878 * null). cgiPathPrefix is defined by setting
879 * the servlet's cgiPathPrefix init parameter.
880 *
881 *
882 * @return
883 * <ul>
884 * <li>
885 * <code>path</code> - full file-system path to valid cgi script,
886 * or null if no cgi was found
887 * <li>
888 * <code>scriptName</code> -
889 * CGI variable SCRIPT_NAME; the full URL path
890 * to valid cgi script or null if no cgi was
891 * found
892 * <li>
893 * <code>cgiName</code> - servlet pathInfo fragment corresponding to
894 * the cgi script itself, or null if not found
895 * <li>
896 * <code>name</code> - simple name (no directories) of the
897 * cgi script, or null if no cgi was found
898 * </ul>
899 *
900 * @since Tomcat 4.0
901 */
902 protected String[] findCGI(String pathInfo, String webAppRootDir,
903 String contextPath, String servletPath,
904 String cgiPathPrefix) {
905 String path = null;
906 String name = null;
907 String scriptname = null;
908 String cginame = "";
909
910 if ((webAppRootDir != null)
911 && (webAppRootDir.lastIndexOf(File.separator) ==
912 (webAppRootDir.length() - 1))) {
913 //strip the trailing "/" from the webAppRootDir
914 webAppRootDir =
915 webAppRootDir.substring(0, (webAppRootDir.length() - 1));
916 }
917
918 if (cgiPathPrefix != null) {
919 webAppRootDir = webAppRootDir + File.separator
920 + cgiPathPrefix;
921 }
922
923 if (debug >= 2) {
924 log("findCGI: path=" + pathInfo + ", " + webAppRootDir);
925 }
926
927 File currentLocation = new File(webAppRootDir);
928 StringTokenizer dirWalker =
929 new StringTokenizer(pathInfo, "/");
930 if (debug >= 3) {
931 log("findCGI: currentLoc=" + currentLocation);
932 }
933 while (!currentLocation.isFile() && dirWalker.hasMoreElements()) {
934 if (debug >= 3) {
935 log("findCGI: currentLoc=" + currentLocation);
936 }
937 String nextElement = (String) dirWalker.nextElement();
938 currentLocation = new File(currentLocation, nextElement);
939 cginame = cginame + "/" + nextElement;
940 }
941 if (!currentLocation.isFile()) {
942 return new String[] { null, null, null, null };
943 } else {
944 if (debug >= 2) {
945 log("findCGI: FOUND cgi at " + currentLocation);
946 }
947 path = currentLocation.getAbsolutePath();
948 name = currentLocation.getName();
949
950 if (".".equals(contextPath)) {
951 scriptname = servletPath;
952 } else {
953 scriptname = contextPath + servletPath;
954 }
955 if (!servletPath.equals(cginame)) {
956 scriptname = scriptname + cginame;
957 }
958 }
959
960 if (debug >= 1) {
961 log("findCGI calc: name=" + name + ", path=" + path
962 + ", scriptname=" + scriptname + ", cginame=" + cginame);
963 }
964 return new String[] { path, scriptname, cginame, name };
965 }
966
967 /**
968 * Constructs the CGI environment to be supplied to the invoked CGI
969 * script; relies heavliy on Servlet API methods and findCGI
970 *
971 * @param req request associated with the CGI
972 * invokation
973 *
974 * @return true if environment was set OK, false if there
975 * was a problem and no environment was set
976 */
977 protected boolean setCGIEnvironment(HttpServletRequest req) throws IOException {
978
979 /*
980 * This method is slightly ugly; c'est la vie.
981 * "You cannot stop [ugliness], you can only hope to contain [it]"
982 * (apologies to Marv Albert regarding MJ)
983 */
984
985 Hashtable<String,String> envp = new Hashtable<String,String>();
986
987 // Add the shell environment variables (if any)
988 envp.putAll(shellEnv);
989
990 // Add the CGI environment variables
991 String sPathInfoOrig = null;
992 String sPathTranslatedOrig = null;
993 String sPathInfoCGI = null;
994 String sPathTranslatedCGI = null;
995 String sCGIFullPath = null;
996 String sCGIScriptName = null;
997 String sCGIFullName = null;
998 String sCGIName = null;
999 String[] sCGINames;
1000
1001
1002 sPathInfoOrig = this.pathInfo;
1003 sPathInfoOrig = sPathInfoOrig == null ? "" : sPathInfoOrig;
1004
1005 sPathTranslatedOrig = req.getPathTranslated();
1006 sPathTranslatedOrig =
1007 sPathTranslatedOrig == null ? "" : sPathTranslatedOrig;
1008
1009 if (webAppRootDir == null ) {
1010 // The app has not been deployed in exploded form
1011 webAppRootDir = tmpDir.toString();
1012 expandCGIScript();
1013 }
1014
1015 sCGINames = findCGI(sPathInfoOrig,
1016 webAppRootDir,
1017 contextPath,
1018 servletPath,
1019 cgiPathPrefix);
1020
1021 sCGIFullPath = sCGINames[0];
1022 sCGIScriptName = sCGINames[1];
1023 sCGIFullName = sCGINames[2];
1024 sCGIName = sCGINames[3];
1025
1026 if (sCGIFullPath == null
1027 || sCGIScriptName == null
1028 || sCGIFullName == null
1029 || sCGIName == null) {
1030 return false;
1031 }
1032
1033 envp.put("SERVER_SOFTWARE", "TOMCAT");
1034
1035 envp.put("SERVER_NAME", nullsToBlanks(req.getServerName()));
1036
1037 envp.put("GATEWAY_INTERFACE", "CGI/1.1");
1038
1039 envp.put("SERVER_PROTOCOL", nullsToBlanks(req.getProtocol()));
1040
1041 int port = req.getServerPort();
1042 Integer iPort = (port == 0 ? new Integer(-1) : new Integer(port));
1043 envp.put("SERVER_PORT", iPort.toString());
1044
1045 envp.put("REQUEST_METHOD", nullsToBlanks(req.getMethod()));
1046
1047 envp.put("REQUEST_URI", nullsToBlanks(req.getRequestURI()));
1048
1049
1050 /*-
1051 * PATH_INFO should be determined by using sCGIFullName:
1052 * 1) Let sCGIFullName not end in a "/" (see method findCGI)
1053 * 2) Let sCGIFullName equal the pathInfo fragment which
1054 * corresponds to the actual cgi script.
1055 * 3) Thus, PATH_INFO = request.getPathInfo().substring(
1056 * sCGIFullName.length())
1057 *
1058 * (see method findCGI, where the real work is done)
1059 *
1060 */
1061 if (pathInfo == null
1062 || (pathInfo.substring(sCGIFullName.length()).length() <= 0)) {
1063 sPathInfoCGI = "";
1064 } else {
1065 sPathInfoCGI = pathInfo.substring(sCGIFullName.length());
1066 }
1067 envp.put("PATH_INFO", sPathInfoCGI);
1068
1069
1070 /*-
1071 * PATH_TRANSLATED must be determined after PATH_INFO (and the
1072 * implied real cgi-script) has been taken into account.
1073 *
1074 * The following example demonstrates:
1075 *
1076 * servlet info = /servlet/cgigw/dir1/dir2/cgi1/trans1/trans2
1077 * cgifullpath = /servlet/cgigw/dir1/dir2/cgi1
1078 * path_info = /trans1/trans2
1079 * webAppRootDir = servletContext.getRealPath("/")
1080 *
1081 * path_translated = servletContext.getRealPath("/trans1/trans2")
1082 *
1083 * That is, PATH_TRANSLATED = webAppRootDir + sPathInfoCGI
1084 * (unless sPathInfoCGI is null or blank, then the CGI
1085 * specification dictates that the PATH_TRANSLATED metavariable
1086 * SHOULD NOT be defined.
1087 *
1088 */
1089 if (sPathInfoCGI != null && !("".equals(sPathInfoCGI))) {
1090 sPathTranslatedCGI = context.getRealPath(sPathInfoCGI);
1091 } else {
1092 sPathTranslatedCGI = null;
1093 }
1094 if (sPathTranslatedCGI == null || "".equals(sPathTranslatedCGI)) {
1095 //NOOP
1096 } else {
1097 envp.put("PATH_TRANSLATED", nullsToBlanks(sPathTranslatedCGI));
1098 }
1099
1100
1101 envp.put("SCRIPT_NAME", nullsToBlanks(sCGIScriptName));
1102
1103 envp.put("QUERY_STRING", nullsToBlanks(req.getQueryString()));
1104
1105 envp.put("REMOTE_HOST", nullsToBlanks(req.getRemoteHost()));
1106
1107 envp.put("REMOTE_ADDR", nullsToBlanks(req.getRemoteAddr()));
1108
1109 envp.put("AUTH_TYPE", nullsToBlanks(req.getAuthType()));
1110
1111 envp.put("REMOTE_USER", nullsToBlanks(req.getRemoteUser()));
1112
1113 envp.put("REMOTE_IDENT", ""); //not necessary for full compliance
1114
1115 envp.put("CONTENT_TYPE", nullsToBlanks(req.getContentType()));
1116
1117
1118 /* Note CGI spec says CONTENT_LENGTH must be NULL ("") or undefined
1119 * if there is no content, so we cannot put 0 or -1 in as per the
1120 * Servlet API spec.
1121 */
1122 int contentLength = req.getContentLength();
1123 String sContentLength = (contentLength <= 0 ? "" :
1124 (new Integer(contentLength)).toString());
1125 envp.put("CONTENT_LENGTH", sContentLength);
1126
1127
1128 Enumeration headers = req.getHeaderNames();
1129 String header = null;
1130 while (headers.hasMoreElements()) {
1131 header = null;
1132 header = ((String) headers.nextElement()).toUpperCase();
1133 //REMIND: rewrite multiple headers as if received as single
1134 //REMIND: change character set
1135 //REMIND: I forgot what the previous REMIND means
1136 if ("AUTHORIZATION".equalsIgnoreCase(header) ||
1137 "PROXY_AUTHORIZATION".equalsIgnoreCase(header)) {
1138 //NOOP per CGI specification section 11.2
1139 } else {
1140 envp.put("HTTP_" + header.replace('-', '_'),
1141 req.getHeader(header));
1142 }
1143 }
1144
1145 File fCGIFullPath = new File(sCGIFullPath);
1146 command = fCGIFullPath.getCanonicalPath();
1147
1148 envp.put("X_TOMCAT_SCRIPT_PATH", command); //for kicks
1149
1150 envp.put("SCRIPT_FILENAME", command); //for PHP
1151
1152 this.env = envp;
1153
1154 return true;
1155
1156 }
1157
1158 /**
1159 * Extracts requested resource from web app archive to context work
1160 * directory to enable CGI script to be executed.
1161 */
1162 protected void expandCGIScript() {
1163 StringBuffer srcPath = new StringBuffer();
1164 StringBuffer destPath = new StringBuffer();
1165 InputStream is = null;
1166
1167 // paths depend on mapping
1168 if (cgiPathPrefix == null ) {
1169 srcPath.append(pathInfo);
1170 is = context.getResourceAsStream(srcPath.toString());
1171 destPath.append(tmpDir);
1172 destPath.append(pathInfo);
1173 } else {
1174 // essentially same search algorithm as findCGI()
1175 srcPath.append(cgiPathPrefix);
1176 StringTokenizer pathWalker =
1177 new StringTokenizer (pathInfo, "/");
1178 // start with first element
1179 while (pathWalker.hasMoreElements() && (is == null)) {
1180 srcPath.append("/");
1181 srcPath.append(pathWalker.nextElement());
1182 is = context.getResourceAsStream(srcPath.toString());
1183 }
1184 destPath.append(tmpDir);
1185 destPath.append("/");
1186 destPath.append(srcPath);
1187 }
1188
1189 if (is == null) {
1190 // didn't find anything, give up now
1191 if (debug >= 2) {
1192 log("expandCGIScript: source '" + srcPath + "' not found");
1193 }
1194 return;
1195 }
1196
1197 File f = new File(destPath.toString());
1198 if (f.exists()) {
1199 // Don't need to expand if it already exists
1200 return;
1201 }
1202
1203 // create directories
1204 String dirPath = new String (destPath.toString().substring(
1205 0,destPath.toString().lastIndexOf("/")));
1206 File dir = new File(dirPath);
1207 dir.mkdirs();
1208
1209 try {
1210 synchronized (expandFileLock) {
1211 // make sure file doesn't exist
1212 if (f.exists()) {
1213 return;
1214 }
1215
1216 // create file
1217 if (!f.createNewFile()) {
1218 return;
1219 }
1220 FileOutputStream fos = new FileOutputStream(f);
1221
1222 // copy data
1223 IOTools.flow(is, fos);
1224 is.close();
1225 fos.close();
1226 if (debug >= 2) {
1227 log("expandCGIScript: expanded '" + srcPath + "' to '" + destPath + "'");
1228 }
1229 }
1230 } catch (IOException ioe) {
1231 // delete in case file is corrupted
1232 if (f.exists()) {
1233 f.delete();
1234 }
1235 }
1236 }
1237
1238
1239 /**
1240 * Print important CGI environment information in a easy-to-read HTML
1241 * table
1242 *
1243 * @return HTML string containing CGI environment info
1244 *
1245 */
1246 public String toString() {
1247
1248 StringBuffer sb = new StringBuffer();
1249
1250 sb.append("<TABLE border=2>");
1251
1252 sb.append("<tr><th colspan=2 bgcolor=grey>");
1253 sb.append("CGIEnvironment Info</th></tr>");
1254
1255 sb.append("<tr><td>Debug Level</td><td>");
1256 sb.append(debug);
1257 sb.append("</td></tr>");
1258
1259 sb.append("<tr><td>Validity:</td><td>");
1260 sb.append(isValid());
1261 sb.append("</td></tr>");
1262
1263 if (isValid()) {
1264 Enumeration envk = env.keys();
1265 while (envk.hasMoreElements()) {
1266 String s = (String) envk.nextElement();
1267 sb.append("<tr><td>");
1268 sb.append(s);
1269 sb.append("</td><td>");
1270 sb.append(blanksToString((String) env.get(s),
1271 "[will be set to blank]"));
1272 sb.append("</td></tr>");
1273 }
1274 }
1275
1276 sb.append("<tr><td colspan=2><HR></td></tr>");
1277
1278 sb.append("<tr><td>Derived Command</td><td>");
1279 sb.append(nullsToBlanks(command));
1280 sb.append("</td></tr>");
1281
1282 sb.append("<tr><td>Working Directory</td><td>");
1283 if (workingDirectory != null) {
1284 sb.append(workingDirectory.toString());
1285 }
1286 sb.append("</td></tr>");
1287
1288 sb.append("<tr><td>Command Line Params</td><td>");
1289 for (int i=0; i < cmdLineParameters.size(); i++) {
1290 String param = (String) cmdLineParameters.get(i);
1291 sb.append("<p>");
1292 sb.append(param);
1293 sb.append("</p>");
1294 }
1295 sb.append("</td></tr>");
1296
1297 sb.append("</TABLE><p>end.");
1298
1299 return sb.toString();
1300 }
1301
1302
1303
1304 /**
1305 * Gets derived command string
1306 *
1307 * @return command string
1308 *
1309 */
1310 protected String getCommand() {
1311 return command;
1312 }
1313
1314
1315
1316 /**
1317 * Gets derived CGI working directory
1318 *
1319 * @return working directory
1320 *
1321 */
1322 protected File getWorkingDirectory() {
1323 return workingDirectory;
1324 }
1325
1326
1327
1328 /**
1329 * Gets derived CGI environment
1330 *
1331 * @return CGI environment
1332 *
1333 */
1334 protected Hashtable getEnvironment() {
1335 return env;
1336 }
1337
1338
1339
1340 /**
1341 * Gets derived CGI query parameters
1342 *
1343 * @return CGI query parameters
1344 *
1345 */
1346 protected ArrayList getParameters() {
1347 return cmdLineParameters;
1348 }
1349
1350
1351
1352 /**
1353 * Gets validity status
1354 *
1355 * @return true if this environment is valid, false
1356 * otherwise
1357 *
1358 */
1359 protected boolean isValid() {
1360 return valid;
1361 }
1362
1363
1364
1365 /**
1366 * Converts null strings to blank strings ("")
1367 *
1368 * @param s string to be converted if necessary
1369 * @return a non-null string, either the original or the empty string
1370 * ("") if the original was <code>null</code>
1371 */
1372 protected String nullsToBlanks(String s) {
1373 return nullsToString(s, "");
1374 }
1375
1376
1377
1378 /**
1379 * Converts null strings to another string
1380 *
1381 * @param couldBeNull string to be converted if necessary
1382 * @param subForNulls string to return instead of a null string
1383 * @return a non-null string, either the original or the substitute
1384 * string if the original was <code>null</code>
1385 */
1386 protected String nullsToString(String couldBeNull,
1387 String subForNulls) {
1388 return (couldBeNull == null ? subForNulls : couldBeNull);
1389 }
1390
1391
1392
1393 /**
1394 * Converts blank strings to another string
1395 *
1396 * @param couldBeBlank string to be converted if necessary
1397 * @param subForBlanks string to return instead of a blank string
1398 * @return a non-null string, either the original or the substitute
1399 * string if the original was <code>null</code> or empty ("")
1400 */
1401 protected String blanksToString(String couldBeBlank,
1402 String subForBlanks) {
1403 return (("".equals(couldBeBlank) || couldBeBlank == null)
1404 ? subForBlanks
1405 : couldBeBlank);
1406 }
1407
1408
1409
1410 } //class CGIEnvironment
1411
1412
1413
1414
1415
1416
1417 /**
1418 * Encapsulates the knowledge of how to run a CGI script, given the
1419 * script's desired environment and (optionally) input/output streams
1420 *
1421 * <p>
1422 *
1423 * Exposes a <code>run</code> method used to actually invoke the
1424 * CGI.
1425 *
1426 * </p>
1427 * <p>
1428 *
1429 * The CGI environment and settings are derived from the information
1430 * passed to the constuctor.
1431 *
1432 * </p>
1433 * <p>
1434 *
1435 * The input and output streams can be set by the <code>setInput</code>
1436 * and <code>setResponse</code> methods, respectively.
1437 * </p>
1438 *
1439 * @version $Revision: 595801 $, $Date: 2007-11-16 21:07:07 +0100 (ven., 16 nov. 2007) $
1440 */
1441
1442 protected class CGIRunner {
1443
1444 /** script/command to be executed */
1445 private String command = null;
1446
1447 /** environment used when invoking the cgi script */
1448 private Hashtable env = null;
1449
1450 /** working directory used when invoking the cgi script */
1451 private File wd = null;
1452
1453 /** command line parameters to be passed to the invoked script */
1454 private ArrayList params = null;
1455
1456 /** stdin to be passed to cgi script */
1457 private InputStream stdin = null;
1458
1459 /** response object used to set headers & get output stream */
1460 private HttpServletResponse response = null;
1461
1462 /** boolean tracking whether this object has enough info to run() */
1463 private boolean readyToRun = false;
1464
1465
1466
1467
1468 /**
1469 * Creates a CGIRunner and initializes its environment, working
1470 * directory, and query parameters.
1471 * <BR>
1472 * Input/output streams (optional) are set using the
1473 * <code>setInput</code> and <code>setResponse</code> methods,
1474 * respectively.
1475 *
1476 * @param command string full path to command to be executed
1477 * @param env Hashtable with the desired script environment
1478 * @param wd File with the script's desired working directory
1479 * @param params ArrayList with the script's query command line
1480 * paramters as strings
1481 */
1482 protected CGIRunner(String command, Hashtable env, File wd,
1483 ArrayList params) {
1484 this.command = command;
1485 this.env = env;
1486 this.wd = wd;
1487 this.params = params;
1488 updateReadyStatus();
1489 }
1490
1491
1492
1493 /**
1494 * Checks & sets ready status
1495 */
1496 protected void updateReadyStatus() {
1497 if (command != null
1498 && env != null
1499 && wd != null
1500 && params != null
1501 && response != null) {
1502 readyToRun = true;
1503 } else {
1504 readyToRun = false;
1505 }
1506 }
1507
1508
1509
1510 /**
1511 * Gets ready status
1512 *
1513 * @return false if not ready (<code>run</code> will throw
1514 * an exception), true if ready
1515 */
1516 protected boolean isReady() {
1517 return readyToRun;
1518 }
1519
1520
1521
1522 /**
1523 * Sets HttpServletResponse object used to set headers and send
1524 * output to
1525 *
1526 * @param response HttpServletResponse to be used
1527 *
1528 */
1529 protected void setResponse(HttpServletResponse response) {
1530 this.response = response;
1531 updateReadyStatus();
1532 }
1533
1534
1535
1536 /**
1537 * Sets standard input to be passed on to the invoked cgi script
1538 *
1539 * @param stdin InputStream to be used
1540 *
1541 */
1542 protected void setInput(InputStream stdin) {
1543 this.stdin = stdin;
1544 updateReadyStatus();
1545 }
1546
1547
1548
1549 /**
1550 * Converts a Hashtable to a String array by converting each
1551 * key/value pair in the Hashtable to a String in the form
1552 * "key=value" (hashkey + "=" + hash.get(hashkey).toString())
1553 *
1554 * @param h Hashtable to convert
1555 *
1556 * @return converted string array
1557 *
1558 * @exception NullPointerException if a hash key has a null value
1559 *
1560 */
1561 protected String[] hashToStringArray(Hashtable h)
1562 throws NullPointerException {
1563 Vector<String> v = new Vector<String>();
1564 Enumeration e = h.keys();
1565 while (e.hasMoreElements()) {
1566 String k = e.nextElement().toString();
1567 v.add(k + "=" + h.get(k));
1568 }
1569 String[] strArr = new String[v.size()];
1570 v.copyInto(strArr);
1571 return strArr;
1572 }
1573
1574
1575
1576 /**
1577 * Executes a CGI script with the desired environment, current working
1578 * directory, and input/output streams
1579 *
1580 * <p>
1581 * This implements the following CGI specification recommedations:
1582 * <UL>
1583 * <LI> Servers SHOULD provide the "<code>query</code>" component of
1584 * the script-URI as command-line arguments to scripts if it
1585 * does not contain any unencoded "=" characters and the
1586 * command-line arguments can be generated in an unambiguous
1587 * manner.
1588 * <LI> Servers SHOULD set the AUTH_TYPE metavariable to the value
1589 * of the "<code>auth-scheme</code>" token of the
1590 * "<code>Authorization</code>" if it was supplied as part of the
1591 * request header. See <code>getCGIEnvironment</code> method.
1592 * <LI> Where applicable, servers SHOULD set the current working
1593 * directory to the directory in which the script is located
1594 * before invoking it.
1595 * <LI> Server implementations SHOULD define their behavior for the
1596 * following cases:
1597 * <ul>
1598 * <LI> <u>Allowed characters in pathInfo</u>: This implementation
1599 * does not allow ASCII NUL nor any character which cannot
1600 * be URL-encoded according to internet standards;
1601 * <LI> <u>Allowed characters in path segments</u>: This
1602 * implementation does not allow non-terminal NULL
1603 * segments in the the path -- IOExceptions may be thrown;
1604 * <LI> <u>"<code>.</code>" and "<code>..</code>" path
1605 * segments</u>:
1606 * This implementation does not allow "<code>.</code>" and
1607 * "<code>..</code>" in the the path, and such characters
1608 * will result in an IOException being thrown;
1609 * <LI> <u>Implementation limitations</u>: This implementation
1610 * does not impose any limitations except as documented
1611 * above. This implementation may be limited by the
1612 * servlet container used to house this implementation.
1613 * In particular, all the primary CGI variable values
1614 * are derived either directly or indirectly from the
1615 * container's implementation of the Servlet API methods.
1616 * </ul>
1617 * </UL>
1618 * </p>
1619 *
1620 * @exception IOException if problems during reading/writing occur
1621 *
1622 * @see java.lang.Runtime#exec(String command, String[] envp,
1623 * File dir)
1624 */
1625 protected void run() throws IOException {
1626
1627 /*
1628 * REMIND: this method feels too big; should it be re-written?
1629 */
1630
1631 if (!isReady()) {
1632 throw new IOException(this.getClass().getName()
1633 + ": not ready to run.");
1634 }
1635
1636 if (debug >= 1 ) {
1637 log("runCGI(envp=[" + env + "], command=" + command + ")");
1638 }
1639
1640 if ((command.indexOf(File.separator + "." + File.separator) >= 0)
1641 || (command.indexOf(File.separator + "..") >= 0)
1642 || (command.indexOf(".." + File.separator) >= 0)) {
1643 throw new IOException(this.getClass().getName()
1644 + "Illegal Character in CGI command "
1645 + "path ('.' or '..') detected. Not "
1646 + "running CGI [" + command + "].");
1647 }
1648
1649 /* original content/structure of this section taken from
1650 * http://developer.java.sun.com/developer/
1651 * bugParade/bugs/4216884.html
1652 * with major modifications by Martin Dengler
1653 */
1654 Runtime rt = null;
1655 InputStream cgiOutput = null;
1656 BufferedReader commandsStdErr = null;
1657 BufferedOutputStream commandsStdIn = null;
1658 Process proc = null;
1659 int bufRead = -1;
1660
1661 //create query arguments
1662 StringBuffer cmdAndArgs = new StringBuffer();
1663 if (command.indexOf(" ") < 0) {
1664 cmdAndArgs.append(command);
1665 } else {
1666 // Spaces used as delimiter, so need to use quotes
1667 cmdAndArgs.append("\"");
1668 cmdAndArgs.append(command);
1669 cmdAndArgs.append("\"");
1670 }
1671
1672 for (int i=0; i < params.size(); i++) {
1673 cmdAndArgs.append(" ");
1674 String param = (String) params.get(i);
1675 if (param.indexOf(" ") < 0) {
1676 cmdAndArgs.append(param);
1677 } else {
1678 // Spaces used as delimiter, so need to use quotes
1679 cmdAndArgs.append("\"");
1680 cmdAndArgs.append(param);
1681 cmdAndArgs.append("\"");
1682 }
1683 }
1684
1685 StringBuffer command = new StringBuffer(cgiExecutable);
1686 command.append(" ");
1687 command.append(cmdAndArgs.toString());
1688 cmdAndArgs = command;
1689
1690 try {
1691 rt = Runtime.getRuntime();
1692 proc = rt.exec(cmdAndArgs.toString(), hashToStringArray(env), wd);
1693
1694 String sContentLength = (String) env.get("CONTENT_LENGTH");
1695
1696 if(!"".equals(sContentLength)) {
1697 commandsStdIn = new BufferedOutputStream(proc.getOutputStream());
1698 IOTools.flow(stdin, commandsStdIn);
1699 commandsStdIn.flush();
1700 commandsStdIn.close();
1701 }
1702
1703 /* we want to wait for the process to exit, Process.waitFor()
1704 * is useless in our situation; see
1705 * http://developer.java.sun.com/developer/
1706 * bugParade/bugs/4223650.html
1707 */
1708
1709 boolean isRunning = true;
1710 commandsStdErr = new BufferedReader
1711 (new InputStreamReader(proc.getErrorStream()));
1712 final BufferedReader stdErrRdr = commandsStdErr ;
1713
1714 new Thread() {
1715 public void run () {
1716 sendToLog(stdErrRdr) ;
1717 } ;
1718 }.start() ;
1719
1720 InputStream cgiHeaderStream =
1721 new HTTPHeaderInputStream(proc.getInputStream());
1722 BufferedReader cgiHeaderReader =
1723 new BufferedReader(new InputStreamReader(cgiHeaderStream));
1724
1725 while (isRunning) {
1726 try {
1727 //set headers
1728 String line = null;
1729 while (((line = cgiHeaderReader.readLine()) != null)
1730 && !("".equals(line))) {
1731 if (debug >= 2) {
1732 log("runCGI: addHeader(\"" + line + "\")");
1733 }
1734 if (line.startsWith("HTTP")) {
1735 response.setStatus(getSCFromHttpStatusLine(line));
1736 } else if (line.indexOf(":") >= 0) {
1737 String header =
1738 line.substring(0, line.indexOf(":")).trim();
1739 String value =
1740 line.substring(line.indexOf(":") + 1).trim();
1741 if (header.equalsIgnoreCase("status")) {
1742 response.setStatus(getSCFromCGIStatusHeader(value));
1743 } else {
1744 response.addHeader(header , value);
1745 }
1746 } else {
1747 log("runCGI: bad header line \"" + line + "\"");
1748 }
1749 }
1750
1751 //write output
1752 byte[] bBuf = new byte[2048];
1753
1754 OutputStream out = response.getOutputStream();
1755 cgiOutput = proc.getInputStream();
1756
1757 try {
1758 while ((bufRead = cgiOutput.read(bBuf)) != -1) {
1759 if (debug >= 4) {
1760 log("runCGI: output " + bufRead +
1761 " bytes of data");
1762 }
1763 out.write(bBuf, 0, bufRead);
1764 }
1765 } finally {
1766 // Attempt to consume any leftover byte if something bad happens,
1767 // such as a socket disconnect on the servlet side; otherwise, the
1768 // external process could hang
1769 if (bufRead != -1) {
1770 while ((bufRead = cgiOutput.read(bBuf)) != -1) {}
1771 }
1772 }
1773
1774 proc.exitValue(); // Throws exception if alive
1775
1776 isRunning = false;
1777
1778 } catch (IllegalThreadStateException e) {
1779 try {
1780 Thread.sleep(500);
1781 } catch (InterruptedException ignored) {
1782 }
1783 }
1784 } //replacement for Process.waitFor()
1785
1786 // Close the output stream used
1787 cgiOutput.close();
1788 }
1789 catch (IOException e){
1790 log ("Caught exception " + e);
1791 throw e;
1792 }
1793 finally{
1794 if (debug > 4) {
1795 log ("Running finally block");
1796 }
1797 if (proc != null){
1798 proc.destroy();
1799 proc = null;
1800 }
1801 }
1802 }
1803
1804 /**
1805 * Parses the Status-Line and extracts the status code.
1806 *
1807 * @param line The HTTP Status-Line (RFC2616, section 6.1)
1808 * @return The extracted status code or the code representing an
1809 * internal error if a valid status code cannot be extracted.
1810 */
1811 private int getSCFromHttpStatusLine(String line) {
1812 int statusStart = line.indexOf(' ') + 1;
1813
1814 if (statusStart < 1 || line.length() < statusStart + 3) {
1815 // Not a valid HTTP Status-Line
1816 log ("runCGI: invalid HTTP Status-Line:" + line);
1817 return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
1818 }
1819
1820 String status = line.substring(statusStart, statusStart + 3);
1821
1822 int statusCode;
1823 try {
1824 statusCode = Integer.parseInt(status);
1825 } catch (NumberFormatException nfe) {
1826 // Not a valid status code
1827 log ("runCGI: invalid status code:" + status);
1828 return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
1829 }
1830
1831 return statusCode;
1832 }
1833
1834 /**
1835 * Parses the CGI Status Header value and extracts the status code.
1836 *
1837 * @param value The CGI Status value of the form <code>
1838 * digit digit digit SP reason-phrase</code>
1839 * @return The extracted status code or the code representing an
1840 * internal error if a valid status code cannot be extracted.
1841 */
1842 private int getSCFromCGIStatusHeader(String value) {
1843 if (value.length() < 3) {
1844 // Not a valid status value
1845 log ("runCGI: invalid status value:" + value);
1846 return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
1847 }
1848
1849 String status = value.substring(0, 3);
1850
1851 int statusCode;
1852 try {
1853 statusCode = Integer.parseInt(status);
1854 } catch (NumberFormatException nfe) {
1855 // Not a valid status code
1856 log ("runCGI: invalid status code:" + status);
1857 return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
1858 }
1859
1860 return statusCode;
1861 }
1862
1863 private void sendToLog(BufferedReader rdr) {
1864 String line = null;
1865 int lineCount = 0 ;
1866 try {
1867 while ((line = rdr.readLine()) != null) {
1868 log("runCGI (stderr):" + line) ;
1869 lineCount++ ;
1870 }
1871 } catch (IOException e) {
1872 log("sendToLog error", e) ;
1873 } finally {
1874 try {
1875 rdr.close() ;
1876 } catch (IOException ce) {
1877 log("sendToLog error", ce) ;
1878 } ;
1879 } ;
1880 if ( lineCount > 0 && debug > 2) {
1881 log("runCGI: " + lineCount + " lines received on stderr") ;
1882 } ;
1883 }
1884 } //class CGIRunner
1885
1886 /**
1887 * This is an input stream specifically for reading HTTP headers. It reads
1888 * upto and including the two blank lines terminating the headers. It
1889 * allows the content to be read using bytes or characters as appropriate.
1890 */
1891 protected class HTTPHeaderInputStream extends InputStream {
1892 private static final int STATE_CHARACTER = 0;
1893 private static final int STATE_FIRST_CR = 1;
1894 private static final int STATE_FIRST_LF = 2;
1895 private static final int STATE_SECOND_CR = 3;
1896 private static final int STATE_HEADER_END = 4;
1897
1898 private InputStream input;
1899 private int state;
1900
1901 HTTPHeaderInputStream(InputStream theInput) {
1902 input = theInput;
1903 state = STATE_CHARACTER;
1904 }
1905
1906 /**
1907 * @see java.io.InputStream#read()
1908 */
1909 public int read() throws IOException {
1910 if (state == STATE_HEADER_END) {
1911 return -1;
1912 }
1913
1914 int i = input.read();
1915
1916 // Update the state
1917 // State machine looks like this
1918 //
1919 // -------->--------
1920 // | (CR) |
1921 // | |
1922 // CR1--->--- |
1923 // | | |
1924 // ^(CR) |(LF) |
1925 // | | |
1926 // CHAR--->--LF1--->--EOH
1927 // (LF) | (LF) |
1928 // |(CR) ^(LF)
1929 // | |
1930 // (CR2)-->---
1931
1932 if (i == 10) {
1933 // LF
1934 switch(state) {
1935 case STATE_CHARACTER:
1936 state = STATE_FIRST_LF;
1937 break;
1938 case STATE_FIRST_CR:
1939 state = STATE_FIRST_LF;
1940 break;
1941 case STATE_FIRST_LF:
1942 case STATE_SECOND_CR:
1943 state = STATE_HEADER_END;
1944 break;
1945 }
1946
1947 } else if (i == 13) {
1948 // CR
1949 switch(state) {
1950 case STATE_CHARACTER:
1951 state = STATE_FIRST_CR;
1952 break;
1953 case STATE_FIRST_CR:
1954 state = STATE_HEADER_END;
1955 break;
1956 case STATE_FIRST_LF:
1957 state = STATE_SECOND_CR;
1958 break;
1959 }
1960
1961 } else {
1962 state = STATE_CHARACTER;
1963 }
1964
1965 return i;
1966 }
1967 } // class HTTPHeaderInputStream
1968
1969 } //class CGIServlet