PageRenderTime 69ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/java/org/browsermob/proxy/jetty/servlet/CGI.java

https://github.com/dereke/browsermob-proxy
Java | 378 lines | 247 code | 52 blank | 79 comment | 41 complexity | a368755caa198df08b23b9dc41d6d562 MD5 | raw file
  1. // ========================================================================
  2. // A very basic CGI Servlet, for use, with Jetty
  3. // (jetty.jetty.org). It's heading towards CGI/1.1 compliance, but
  4. // still lacks a few features - the basic stuff is here though...
  5. // Copyright 2000 Julian Gosnell <jules_gosnell@yahoo.com> Released
  6. // under the terms of the Jetty Licence.
  7. // ========================================================================
  8. // TODO
  9. // - logging
  10. // - child's stderr
  11. // - exceptions should report to client via sendError()
  12. // - tidy up
  13. package org.browsermob.proxy.jetty.servlet;
  14. import org.apache.commons.logging.Log;
  15. import org.browsermob.proxy.jetty.http.HttpFields;
  16. import org.browsermob.proxy.jetty.log.LogFactory;
  17. import org.browsermob.proxy.jetty.util.IO;
  18. import org.browsermob.proxy.jetty.util.LineInput;
  19. import org.browsermob.proxy.jetty.util.LogSupport;
  20. import org.browsermob.proxy.jetty.util.StringUtil;
  21. import javax.servlet.ServletException;
  22. import javax.servlet.http.HttpServlet;
  23. import javax.servlet.http.HttpServletRequest;
  24. import javax.servlet.http.HttpServletResponse;
  25. import java.io.File;
  26. import java.io.IOException;
  27. import java.io.InputStream;
  28. import java.io.OutputStream;
  29. import java.util.Enumeration;
  30. import java.util.HashMap;
  31. import java.util.Iterator;
  32. import java.util.Map;
  33. //-----------------------------------------------------------------------------
  34. /** CGI Servlet.
  35. *
  36. * The cgi bin directory can be set with the cgibinResourceBase init
  37. * parameter or it will default to the resource base of the context.
  38. *
  39. * The "commandPrefix" init parameter may be used to set a prefix to all
  40. * commands passed to exec. This can be used on systems that need assistance
  41. * to execute a particular file type. For example on windows this can be set
  42. * to "perl" so that perl scripts are executed.
  43. *
  44. * The "Path" init param is passed to the exec environment as PATH.
  45. * Note: Must be run unpacked somewhere in the filesystem.
  46. *
  47. * Any initParameter that starts with ENV_ is used to set an environment
  48. * variable with the name stripped of the leading ENV_ and using the init
  49. * parameter value.
  50. *
  51. * @version $Revision: 1.27 $
  52. * @author Julian Gosnell
  53. */
  54. public class CGI extends HttpServlet
  55. {
  56. private static Log log = LogFactory.getLog(CGI.class);
  57. protected File _docRoot;
  58. protected String _path;
  59. protected String _cmdPrefix;
  60. protected EnvList _env;
  61. /* ------------------------------------------------------------ */
  62. public void init()
  63. throws ServletException
  64. {
  65. _env= new EnvList();
  66. _cmdPrefix=getInitParameter("commandPrefix");
  67. String tmp = getInitParameter("cgibinResourceBase");
  68. if (tmp==null)
  69. tmp = getServletContext().getRealPath("/");
  70. if(log.isDebugEnabled())log.debug("CGI: CGI bin "+tmp);
  71. if (tmp==null)
  72. {
  73. log.warn("CGI: no CGI bin !");
  74. throw new ServletException();
  75. }
  76. File dir = new File(tmp);
  77. if (!dir.exists())
  78. {
  79. log.warn("CGI: CGI bin does not exist - "+dir);
  80. throw new ServletException();
  81. }
  82. if (!dir.canRead())
  83. {
  84. log.warn("CGI: CGI bin is not readable - "+dir);
  85. throw new ServletException();
  86. }
  87. if (!dir.isDirectory())
  88. {
  89. log.warn("CGI: CGI bin is not a directory - "+dir);
  90. throw new ServletException();
  91. }
  92. try
  93. {
  94. _docRoot=dir.getCanonicalFile();
  95. if(log.isDebugEnabled())log.debug("CGI: CGI bin accepted - "+_docRoot);
  96. }
  97. catch (IOException e)
  98. {
  99. log.warn("CGI: CGI bin failed - "+dir);
  100. e.printStackTrace();
  101. throw new ServletException();
  102. }
  103. _path=getInitParameter("Path");
  104. if(log.isDebugEnabled())log.debug("CGI: PATH accepted - "+_path);
  105. if (_path != null)
  106. _env.set("PATH", _path);
  107. Enumeration e= getInitParameterNames();
  108. while (e.hasMoreElements())
  109. {
  110. String n= (String)e.nextElement();
  111. if (n != null && n.startsWith("ENV_"))
  112. _env.set(n.substring(4),getInitParameter(n));
  113. }
  114. }
  115. /* ------------------------------------------------------------ */
  116. public void service(HttpServletRequest req, HttpServletResponse res)
  117. throws ServletException, IOException
  118. {
  119. String pathInContext =
  120. StringUtil.nonNull(req.getServletPath()) +
  121. StringUtil.nonNull(req.getPathInfo());
  122. if(log.isDebugEnabled())log.debug("CGI: req.getContextPath() : "+req.getContextPath());
  123. if(log.isDebugEnabled())log.debug("CGI: req.getServletPath() : "+req.getServletPath());
  124. if(log.isDebugEnabled())log.debug("CGI: req.getPathInfo() : "+req.getPathInfo());
  125. if(log.isDebugEnabled())log.debug("CGI: _docRoot : "+_docRoot);
  126. // pathInContext may actually comprises scriptName/pathInfo...We will
  127. // walk backwards up it until we find the script - the rest must
  128. // be the pathInfo;
  129. String both=pathInContext;
  130. String first=both;
  131. String last="";
  132. File exe=new File(_docRoot, first);
  133. while ((first.endsWith("/") || !exe.exists()) && first.length()>=0)
  134. {
  135. int index=first.lastIndexOf('/');
  136. first=first.substring(0, index);
  137. last=both.substring(index, both.length());
  138. exe=new File(_docRoot, first);
  139. }
  140. if (first.length()==0 ||
  141. !exe.exists() ||
  142. !exe.getCanonicalPath().equals(exe.getAbsolutePath()) ||
  143. exe.isDirectory())
  144. res.sendError(404);
  145. else
  146. {
  147. if(log.isDebugEnabled())log.debug("CGI: script is "+exe);
  148. if(log.isDebugEnabled())log.debug("CGI: pathInfo is "+last);
  149. exec(exe, last, req, res);
  150. }
  151. }
  152. /* ------------------------------------------------------------ */
  153. /*
  154. * @param root
  155. * @param path
  156. * @param req
  157. * @param res
  158. * @exception IOException
  159. */
  160. private void exec(File command,
  161. String pathInfo,
  162. HttpServletRequest req,
  163. HttpServletResponse res)
  164. throws IOException
  165. {
  166. String path=command.toString();
  167. File dir=command.getParentFile();
  168. if(log.isDebugEnabled())log.debug("CGI: execing: "+path);
  169. EnvList env = new EnvList(_env);
  170. // these ones are from "The WWW Common Gateway Interface Version 1.1"
  171. // look at : http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html#6.1.1
  172. env.set("AUTH_TYPE", req.getAuthType());
  173. env.set("CONTENT_LENGTH", Integer.toString(req.getContentLength()));
  174. env.set("CONTENT_TYPE", req.getContentType());
  175. env.set("GATEWAY_INTERFACE", "CGI/1.1");
  176. env.set("PATH_INFO", pathInfo);
  177. env.set("PATH_TRANSLATED", req.getPathTranslated());
  178. env.set("QUERY_STRING", req.getQueryString());
  179. env.set("REMOTE_ADDR", req.getRemoteAddr());
  180. env.set("REMOTE_HOST", req.getRemoteHost());
  181. // The identity information reported about the connection by a
  182. // RFC 1413 [11] request to the remote agent, if
  183. // available. Servers MAY choose not to support this feature, or
  184. // not to request the data for efficiency reasons.
  185. // "REMOTE_IDENT" => "NYI"
  186. env.set("REMOTE_USER", req.getRemoteUser());
  187. env.set("REQUEST_METHOD", req.getMethod());
  188. String scriptName = req.getRequestURI().substring(0,req.getRequestURI().length() - pathInfo.length());
  189. env.set("SCRIPT_NAME",scriptName);
  190. env.set("SCRIPT_FILENAME",getServletContext().getRealPath(scriptName));
  191. env.set("SERVER_NAME", req.getServerName());
  192. env.set("SERVER_PORT", Integer.toString(req.getServerPort()));
  193. env.set("SERVER_PROTOCOL", req.getProtocol());
  194. env.set("SERVER_SOFTWARE", getServletContext().getServerInfo());
  195. Enumeration enm = req.getHeaderNames();
  196. while (enm.hasMoreElements())
  197. {
  198. String name = (String) enm.nextElement();
  199. String value = req.getHeader(name);
  200. env.set("HTTP_" + name.toUpperCase().replace( '-', '_' ), value);
  201. }
  202. // these extra ones were from printenv on www.dev.nomura.co.uk
  203. env.set("HTTPS", (req.isSecure()?"ON":"OFF"));
  204. // "DOCUMENT_ROOT" => root + "/docs",
  205. // "SERVER_URL" => "NYI - http://us0245",
  206. // "TZ" => System.getProperty("user.timezone"),
  207. // are we meant to decode args here ? or does the script get them
  208. // via PATH_INFO ? if we are, they should be decoded and passed
  209. // into exec here...
  210. String execCmd=path;
  211. if (execCmd.indexOf(" ")>=0)
  212. execCmd="\""+execCmd+"\"";
  213. if (_cmdPrefix!=null)
  214. execCmd=_cmdPrefix+" "+execCmd;
  215. Process p=dir==null
  216. ?Runtime.getRuntime().exec(execCmd, env.getEnvArray())
  217. :Runtime.getRuntime().exec(execCmd, env.getEnvArray(),dir);
  218. // hook processes input to browser's output (async)
  219. final InputStream inFromReq=req.getInputStream();
  220. final OutputStream outToCgi=p.getOutputStream();
  221. final int inputLength = req.getContentLength();
  222. new Thread(new Runnable()
  223. {
  224. public void run()
  225. {
  226. try{
  227. if (inputLength>0)
  228. IO.copy(inFromReq,outToCgi,inputLength);
  229. outToCgi.close();
  230. }
  231. catch(IOException e){LogSupport.ignore(log,e);}
  232. }
  233. }).start();
  234. // hook processes output to browser's input (sync)
  235. // if browser closes stream, we should detect it and kill process...
  236. try
  237. {
  238. // read any headers off the top of our input stream
  239. LineInput li = new LineInput(p.getInputStream());
  240. HttpFields fields=new HttpFields();
  241. fields.read(li);
  242. String ContentStatus = "Status";
  243. String redirect = fields.get(HttpFields.__Location);
  244. String status = fields.get(ContentStatus);
  245. if (status!=null)
  246. {
  247. log.debug("Found a Status header - setting status on response");
  248. fields.remove(ContentStatus);
  249. // NOTE: we ignore any reason phrase, otherwise we
  250. // would need to use res.sendError() selectively.
  251. int i = status.indexOf(' ');
  252. if (i>0)
  253. status = status.substring(0,i);
  254. res.setStatus(Integer.parseInt(status));
  255. }
  256. // copy remaining headers into response...
  257. for (Iterator i=fields.iterator(); i.hasNext();)
  258. {
  259. HttpFields.Entry e=(HttpFields.Entry)i.next();
  260. res.addHeader(e.getKey(),e.getValue());
  261. }
  262. if (status==null && redirect != null)
  263. {
  264. // The CGI has set Location and is counting on us to do the redirect.
  265. // See http://CGI-Spec.Golux.Com/draft-coar-cgi-v11-03-clean.html#7.2.1.2
  266. if (!redirect.startsWith("http:/")&&!redirect.startsWith("https:/"))
  267. res.sendRedirect(redirect);
  268. else
  269. res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
  270. }
  271. // copy remains of input onto output...
  272. IO.copy(li, res.getOutputStream());
  273. p.waitFor();
  274. int exitValue = p.exitValue();
  275. if(log.isDebugEnabled())log.debug("CGI: p.exitValue(): " + exitValue);
  276. if (0 != exitValue)
  277. {
  278. log.warn("Non-zero exit status ("+exitValue+
  279. ") from CGI program: "+path);
  280. if (!res.isCommitted())
  281. res.sendError(500, "Failed to exec CGI");
  282. }
  283. }
  284. catch (IOException e)
  285. {
  286. // browser has probably closed its input stream - we
  287. // terminate and clean up...
  288. log.debug("CGI: Client closed connection!");
  289. }
  290. catch (InterruptedException ie)
  291. {
  292. log.debug("CGI: interrupted!");
  293. }
  294. finally
  295. {
  296. p.destroy();
  297. }
  298. if(log.isDebugEnabled())log.debug("CGI: Finished exec: " + p);
  299. }
  300. /* ------------------------------------------------------------ */
  301. /** private utility class that manages the Environment passed
  302. * to exec.
  303. */
  304. private static class EnvList
  305. {
  306. private Map envMap;
  307. EnvList()
  308. {
  309. envMap= new HashMap();
  310. }
  311. EnvList(EnvList l)
  312. {
  313. envMap= new HashMap(l.envMap);
  314. }
  315. /** Set a name/value pair, null values will be treated as
  316. * an empty String */
  317. public void set(String name, String value) {
  318. envMap.put(name, name + "=" + StringUtil.nonNull(value));
  319. }
  320. /** Get representation suitable for passing to exec. */
  321. public String[] getEnvArray()
  322. {
  323. return (String[])envMap.values().toArray(new String[envMap.size()]);
  324. }
  325. }
  326. }