001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.servlets;
031:
032: import com.caucho.log.Log;
033: import com.caucho.util.Alarm;
034: import com.caucho.util.AlarmListener;
035: import com.caucho.util.CharBuffer;
036: import com.caucho.util.L10N;
037: import com.caucho.vfs.Path;
038: import com.caucho.vfs.ReadStream;
039: import com.caucho.vfs.TempBuffer;
040: import com.caucho.vfs.Vfs;
041:
042: import javax.servlet.GenericServlet;
043: import javax.servlet.ServletException;
044: import javax.servlet.ServletInputStream;
045: import javax.servlet.ServletRequest;
046: import javax.servlet.ServletResponse;
047: import javax.servlet.http.HttpServletRequest;
048: import javax.servlet.http.HttpServletResponse;
049: import java.io.File;
050: import java.io.IOException;
051: import java.io.InputStream;
052: import java.io.OutputStream;
053: import java.util.ArrayList;
054: import java.util.Enumeration;
055: import java.util.logging.Level;
056: import java.util.logging.Logger;
057:
058: /**
059: * CGI
060: */
061: public class CGIServlet extends GenericServlet {
062: static protected final Logger log = Log.open(CGIServlet.class);
063: static final L10N L = new L10N(CGIServlet.class);
064:
065: private static String REQUEST_URI = "javax.servlet.include.request_uri";
066: private static String CONTEXT_PATH = "javax.servlet.include.context_path";
067: private static String SERVLET_PATH = "javax.servlet.include.servlet_path";
068: private static String PATH_INFO = "javax.servlet.include.path_info";
069: private static String QUERY_STRING = "javax.servlet.include.query_string";
070:
071: private String _executable;
072: private boolean _stderrIsException = true;
073: private boolean _ignoreExitCode = false;
074:
075: /**
076: * Sets an executable to run the script.
077: */
078: public void setExecutable(String executable) {
079: _executable = executable;
080: }
081:
082: public void setStderrIsException(boolean isException) {
083: _stderrIsException = isException;
084: }
085:
086: /**
087: * If true, do not treat a non-zero exit code as an error, default false.
088: */
089: public void setIgnoreExitCode(boolean ignoreExitCode) {
090: _ignoreExitCode = ignoreExitCode;
091: }
092:
093: /**
094: * Handle the request.
095: */
096: public void service(ServletRequest request, ServletResponse response)
097: throws ServletException, IOException {
098: HttpServletRequest req = (HttpServletRequest) request;
099: HttpServletResponse res = (HttpServletResponse) response;
100:
101: String requestURI;
102: String contextPath;
103: String servletPath;
104: String servletPathInfo;
105: String queryString;
106:
107: requestURI = (String) req.getAttribute(REQUEST_URI);
108:
109: if (requestURI != null) {
110: contextPath = (String) req.getAttribute(CONTEXT_PATH);
111: servletPath = (String) req.getAttribute(SERVLET_PATH);
112: servletPathInfo = (String) req.getAttribute(PATH_INFO);
113: queryString = (String) req.getAttribute(QUERY_STRING);
114: } else {
115: requestURI = req.getRequestURI();
116: contextPath = req.getContextPath();
117: servletPath = req.getServletPath();
118: servletPathInfo = req.getPathInfo();
119: queryString = req.getQueryString();
120: }
121:
122: String scriptPath;
123: String pathInfo;
124:
125: if (servletPathInfo == null) {
126: scriptPath = servletPath;
127: pathInfo = null;
128: } else {
129: String fullPath = servletPath + servletPathInfo;
130: int i = findScriptPathIndex(req, fullPath);
131:
132: if (i < 0) {
133: if (log.isLoggable(Level.FINE))
134: log.fine(L.l("no script path index for `{0}'",
135: fullPath));
136:
137: res.sendError(res.SC_NOT_FOUND);
138:
139: return;
140: }
141:
142: scriptPath = fullPath.substring(0, i);
143: pathInfo = fullPath.substring(i);
144:
145: if ("".equals(pathInfo))
146: pathInfo = null;
147: }
148:
149: String realPath = req.getRealPath(scriptPath);
150:
151: Path vfsPath = Vfs.lookup(realPath);
152:
153: if (!vfsPath.canRead() || vfsPath.isDirectory()) {
154: if (log.isLoggable(Level.FINE))
155: log.fine(L.l("script `{0}' is unreadable", vfsPath));
156:
157: res.sendError(res.SC_NOT_FOUND);
158:
159: return;
160: }
161:
162: String[] env = createEnvironment(req, requestURI, contextPath,
163: scriptPath, pathInfo, queryString);
164:
165: String[] args = getArgs(realPath);
166:
167: if (log.isLoggable(Level.FINER)) {
168: if (args.length > 1)
169: log.finer("[cgi] exec " + args[0] + " " + args[1]);
170: else if (args.length > 0)
171: log.finer("[cgi] exec " + args[0]);
172: }
173:
174: Runtime runtime = Runtime.getRuntime();
175: Process process = null;
176: Alarm alarm = null;
177:
178: try {
179: File dir = new File(Vfs.lookup(realPath).getParent()
180: .getNativePath());
181:
182: if (log.isLoggable(Level.FINE)) {
183: CharBuffer argsBuf = new CharBuffer();
184:
185: argsBuf.append('[');
186:
187: for (String arg : args) {
188: if (argsBuf.length() > 1)
189: argsBuf.append(", ");
190:
191: argsBuf.append('"');
192: argsBuf.append(arg);
193: argsBuf.append('"');
194: }
195:
196: argsBuf.append(']');
197:
198: log.fine(L.l("exec {0} (pwd={1})", argsBuf, dir));
199:
200: if (log.isLoggable(Level.FINEST)) {
201: for (String envElement : env)
202: log.finest(envElement);
203: }
204: }
205:
206: process = runtime.exec(args, env, dir);
207:
208: InputStream inputStream = process.getInputStream();
209: InputStream errorStream = process.getErrorStream();
210:
211: TimeoutAlarm timeout;
212: timeout = new TimeoutAlarm(requestURI, process, inputStream);
213: alarm = new Alarm(timeout, 360 * 1000);
214:
215: OutputStream outputStream = process.getOutputStream();
216:
217: TempBuffer tempBuf = TempBuffer.allocate();
218: byte[] buf = tempBuf.getBuffer();
219:
220: try {
221: ServletInputStream sis = req.getInputStream();
222: int len;
223:
224: while ((len = sis.read(buf, 0, buf.length)) > 0) {
225: outputStream.write(buf, 0, len);
226: }
227:
228: outputStream.flush();
229: } catch (IOException e) {
230: log.log(Level.FINER, e.toString(), e);
231: } finally {
232: outputStream.close();
233: }
234:
235: TempBuffer.free(tempBuf);
236: tempBuf = null;
237:
238: ReadStream rs = Vfs.openRead(inputStream);
239: boolean hasStatus = false;
240:
241: try {
242: hasStatus = parseHeaders(req, res, rs);
243:
244: OutputStream out = res.getOutputStream();
245:
246: rs.writeToStream(out);
247: } finally {
248: try {
249: rs.close();
250: } catch (Throwable e) {
251: log.log(Level.FINER, e.toString(), e);
252:
253: }
254:
255: inputStream.close();
256: }
257:
258: StringBuilder error = new StringBuilder();
259: boolean hasContent = false;
260: int ch;
261:
262: while (errorStream.available() > 0
263: && (ch = errorStream.read()) > 0) {
264: error.append((char) ch);
265:
266: if (!Character.isWhitespace((char) ch))
267: hasContent = true;
268: }
269: errorStream.close();
270:
271: if (hasContent) {
272: String errorString = error.toString();
273:
274: log.warning(errorString);
275:
276: if (!hasStatus && _stderrIsException)
277: throw new ServletException(errorString);
278: }
279:
280: int exitCode = process.waitFor();
281:
282: if (exitCode != 0) {
283: if (hasStatus) {
284: if (log.isLoggable(Level.FINER))
285: log.finer(L.l(
286: "exit code {0} (ignored, hasStatus)",
287: exitCode));
288: } else if (_ignoreExitCode) {
289: if (log.isLoggable(Level.FINER))
290: log.finer(L.l("exit code {0} (ignored)",
291: exitCode));
292: } else
293: throw new ServletException(L.l(
294: "CGI execution failed. Exit code {0}",
295: exitCode));
296: }
297: } catch (IOException e) {
298: throw e;
299: } catch (ServletException e) {
300: throw e;
301: } catch (Exception e) {
302: throw new ServletException(e);
303: } finally {
304: if (alarm != null)
305: alarm.dequeue();
306:
307: try {
308: process.destroy();
309: } catch (Throwable e) {
310: }
311: }
312: }
313:
314: /**
315: * Returns the index to the script path.
316: */
317: private int findScriptPathIndex(HttpServletRequest req,
318: String fullPath) {
319: String realPath = req.getRealPath(fullPath);
320: Path path = Vfs.lookup(realPath);
321:
322: if (log.isLoggable(Level.FINER))
323: log.finer(L.l("real-path is `{0}'", path));
324:
325: if (path.canRead() && !path.isDirectory())
326: return fullPath.length();
327:
328: int tail = fullPath.length();
329: int head;
330:
331: while ((head = fullPath.lastIndexOf('/', tail)) >= 0) {
332: String subPath = fullPath.substring(0, head);
333:
334: realPath = req.getRealPath(subPath);
335: path = Vfs.lookup(realPath);
336:
337: if (log.isLoggable(Level.FINEST))
338: log.finest(L.l("trying script path {0}", path));
339:
340: if (path.canRead() && !path.isDirectory())
341: return head;
342:
343: tail = head - 1;
344: }
345:
346: return -1;
347: }
348:
349: private String[] getArgs(String path) {
350: if (_executable != null)
351: return new String[] { _executable, path };
352:
353: ReadStream is = null;
354: try {
355: is = Vfs.lookup(path).openRead();
356:
357: int ch;
358: if (is.read() != '#')
359: return new String[] { path };
360: else if (is.read() != '!')
361: return new String[] { path };
362:
363: CharBuffer cb = CharBuffer.allocate();
364: ArrayList<String> list = new ArrayList<String>();
365: ch = is.read();
366:
367: while ((ch >= 0 && ch != '\r' && ch != '\n')) {
368: for (; ch == ' ' || ch == '\t'; ch = is.read()) {
369: }
370:
371: if (ch < 0 || ch == '\r' || ch == '\n') {
372: if (list.size() > 0) {
373: list.add(path);
374: return list.toArray(new String[list.size()]);
375: } else
376: return new String[] { path };
377: }
378:
379: cb.clear();
380: while (ch > 0 && ch != ' ' && ch != '\t' && ch != '\r'
381: && ch != '\n') {
382: cb.append((char) ch);
383:
384: ch = is.read();
385: }
386:
387: list.add(cb.toString());
388:
389: for (; ch == ' ' || ch == '\t'; ch = is.read()) {
390: }
391: }
392:
393: if (list.size() > 0) {
394: list.add(path);
395: return list.toArray(new String[list.size()]);
396: } else
397: return new String[] { path };
398: } catch (Exception e) {
399: return new String[] { path };
400: } finally {
401: if (is != null) {
402: is.close();
403: }
404: }
405: }
406:
407: private String[] createEnvironment(HttpServletRequest req,
408: String requestURI, String contextPath, String scriptPath,
409: String pathInfo, String queryString) {
410: boolean isFine = log.isLoggable(Level.FINE);
411:
412: ArrayList<String> env = new ArrayList<String>();
413:
414: env.add("SERVER_SOFTWARE=Resin/" + com.caucho.Version.VERSION);
415:
416: env.add("SERVER_NAME=" + req.getServerName());
417: //env.add("SERVER_ADDR=" + req.getServerAddr());
418: env.add("SERVER_PORT=" + req.getServerPort());
419:
420: env.add("REMOTE_ADDR=" + req.getRemoteAddr());
421: // env.add("REMOTE_PORT=" + req.getRemotePort());
422:
423: if (req.getRemoteUser() != null)
424: env.add("REMOTE_USER=" + req.getRemoteUser());
425: if (req.getAuthType() != null)
426: env.add("AUTH_TYPE=" + req.getAuthType());
427:
428: env.add("GATEWAY_INTERFACE=CGI/1.1");
429: env.add("SERVER_PROTOCOL=" + req.getProtocol());
430: env.add("REQUEST_METHOD=" + req.getMethod());
431: if (isFine)
432: log.fine("[cgi] REQUEST_METHOD=" + req.getMethod());
433:
434: if (queryString != null) {
435: env.add("QUERY_STRING=" + queryString);
436:
437: if (isFine)
438: log.fine("[cgi] QUERY_STRING=" + queryString);
439: }
440:
441: env.add("REQUEST_URI=" + requestURI);
442:
443: if (isFine)
444: log.fine("[cgi] REQUEST_URI=" + requestURI);
445:
446: // PHP needs SCRIPT_FILENAME or it reports "No input file specified."
447: env.add("SCRIPT_FILENAME=" + req.getRealPath(scriptPath));
448:
449: scriptPath = contextPath + scriptPath;
450:
451: env.add("SCRIPT_NAME=" + scriptPath);
452:
453: if (isFine)
454: log.fine("[cgi] SCRIPT_NAME=" + scriptPath);
455:
456: if (pathInfo != null) {
457: env.add("PATH_INFO=" + pathInfo);
458: env.add("PATH_TRANSLATED=" + req.getRealPath(pathInfo));
459: }
460:
461: Enumeration e = req.getHeaderNames();
462: while (e.hasMoreElements()) {
463: String key = (String) e.nextElement();
464: String value = req.getHeader(key);
465:
466: if (isFine)
467: log.fine("[cgi] " + key + "=" + value);
468:
469: if (key.equalsIgnoreCase("content-length"))
470: env.add("CONTENT_LENGTH=" + value);
471: else if (key.equalsIgnoreCase("content-type"))
472: env.add("CONTENT_TYPE=" + value);
473: else if (key.equalsIgnoreCase("authorization")) {
474: } else if (key.equalsIgnoreCase("proxy-authorization")) {
475: } else
476: env.add(convertHeader(key, value));
477: }
478:
479: return (String[]) env.toArray(new String[env.size()]);
480: }
481:
482: private String convertHeader(String key, String value) {
483: CharBuffer cb = new CharBuffer();
484:
485: cb.append("HTTP_");
486:
487: for (int i = 0; i < key.length(); i++) {
488: char ch = key.charAt(i);
489: if (ch == '-')
490: cb.append('_');
491: else if (ch >= 'a' && ch <= 'z')
492: cb.append((char) (ch + 'A' - 'a'));
493: else
494: cb.append(ch);
495: }
496:
497: cb.append('=');
498: cb.append(value);
499:
500: return cb.close();
501: }
502:
503: private boolean parseHeaders(HttpServletRequest req,
504: HttpServletResponse res, ReadStream rs) throws IOException {
505: boolean hasStatus = false;
506:
507: CharBuffer key = new CharBuffer();
508: CharBuffer value = new CharBuffer();
509:
510: int ch;
511:
512: while (true) {
513: key.clear();
514: value.clear();
515:
516: for (ch = rs.read(); ch >= 0 && ch != ' ' && ch != '\r'
517: && ch != '\n' && ch != ':'; ch = rs.read()) {
518: key.append((char) ch);
519: }
520:
521: for (; ch >= 0 && ch == ' ' || ch == ':'; ch = rs.read()) {
522: }
523:
524: for (; ch >= 0 && ch != '\r' && ch != '\n'; ch = rs.read()) {
525: value.append((char) ch);
526: }
527:
528: if (ch == '\r') {
529: ch = rs.read();
530: if (ch != '\n')
531: rs.unread();
532: }
533:
534: if (key.length() == 0)
535: return hasStatus;
536:
537: String keyStr = key.toString();
538: String valueStr = value.toString();
539:
540: if (log.isLoggable(Level.FINER))
541: log.finer(keyStr + ": " + valueStr);
542:
543: if (keyStr.equalsIgnoreCase("Status")) {
544: int status = 0;
545: int len = valueStr.length();
546: int i = 0;
547:
548: hasStatus = true;
549:
550: for (; i < len && (ch = valueStr.charAt(i)) >= '0'
551: && ch <= '9'; i++)
552: status = 10 * status + ch - '0';
553:
554: for (; i < len && (ch = valueStr.charAt(i)) == ' '; i++) {
555: }
556:
557: if (status < 304)
558: res.setStatus(status);
559: else
560: res.sendError(status, valueStr.substring(i));
561: } else if (keyStr.equalsIgnoreCase("Location")) {
562: String uri;
563:
564: if (valueStr.startsWith("/"))
565: uri = req.getContextPath() + valueStr;
566: else
567: uri = valueStr;
568:
569: res.setHeader("Location", res.encodeRedirectURL(uri));
570: } else
571: res.addHeader(keyStr, valueStr);
572: }
573: }
574:
575: class TimeoutAlarm implements AlarmListener {
576: String _uri;
577: Process _process;
578: InputStream _is;
579:
580: TimeoutAlarm(String uri, Process process, InputStream is) {
581: _uri = uri;
582: _process = process;
583: _is = is;
584: }
585:
586: public void handleAlarm(Alarm alarm) {
587: log.warning("timing out CGI process for '" + _uri + "'");
588:
589: try {
590: _is.close();
591: } catch (Throwable e) {
592: log.log(Level.WARNING, e.toString(), e);
593: }
594:
595: try {
596: _process.destroy();
597: } catch (Throwable e) {
598: log.log(Level.WARNING, e.toString(), e);
599: }
600: }
601: }
602: }
|