1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.servlets;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.io.OutputStreamWriter;
26 import java.io.Writer;
27 import java.util.Enumeration;
28 import java.util.HashMap;
29 import java.util.Locale;
30 import java.util.Map;
31
32 import javax.servlet.ServletException;
33 import javax.servlet.http.HttpServlet;
34 import javax.servlet.http.HttpServletRequest;
35 import javax.servlet.http.HttpServletResponse;
36
37 import org.eclipse.jetty.http.HttpMethods;
38 import org.eclipse.jetty.util.IO;
39 import org.eclipse.jetty.util.MultiMap;
40 import org.eclipse.jetty.util.StringUtil;
41 import org.eclipse.jetty.util.UrlEncoded;
42 import org.eclipse.jetty.util.log.Log;
43 import org.eclipse.jetty.util.log.Logger;
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 public class CGI extends HttpServlet
62 {
63
64
65
66 private static final long serialVersionUID = -6182088932884791073L;
67
68 private static final Logger LOG = Log.getLogger(CGI.class);
69
70 private boolean _ok;
71 private File _docRoot;
72 private String _path;
73 private String _cmdPrefix;
74 private EnvList _env;
75 private boolean _ignoreExitState;
76 private boolean _relative;
77
78
79 @Override
80 public void init() throws ServletException
81 {
82 _env = new EnvList();
83 _cmdPrefix = getInitParameter("commandPrefix");
84 _relative = Boolean.parseBoolean(getInitParameter("cgibinResourceBaseIsRelative"));
85
86 String tmp = getInitParameter("cgibinResourceBase");
87 if (tmp == null)
88 {
89 tmp = getInitParameter("resourceBase");
90 if (tmp == null)
91 tmp = getServletContext().getRealPath("/");
92 }
93 else if (_relative)
94 {
95 tmp = getServletContext().getRealPath(tmp);
96 }
97
98 if (tmp == null)
99 {
100 LOG.warn("CGI: no CGI bin !");
101 return;
102 }
103
104 File dir = new File(tmp);
105 if (!dir.exists())
106 {
107 LOG.warn("CGI: CGI bin does not exist - " + dir);
108 return;
109 }
110
111 if (!dir.canRead())
112 {
113 LOG.warn("CGI: CGI bin is not readable - " + dir);
114 return;
115 }
116
117 if (!dir.isDirectory())
118 {
119 LOG.warn("CGI: CGI bin is not a directory - " + dir);
120 return;
121 }
122
123 try
124 {
125 _docRoot = dir.getCanonicalFile();
126 }
127 catch (IOException e)
128 {
129 LOG.warn("CGI: CGI bin failed - " + dir,e);
130 return;
131 }
132
133 _path = getInitParameter("Path");
134 if (_path != null)
135 _env.set("PATH",_path);
136
137 _ignoreExitState = "true".equalsIgnoreCase(getInitParameter("ignoreExitState"));
138 Enumeration e = getInitParameterNames();
139 while (e.hasMoreElements())
140 {
141 String n = (String)e.nextElement();
142 if (n != null && n.startsWith("ENV_"))
143 _env.set(n.substring(4),getInitParameter(n));
144 }
145 if (!_env.envMap.containsKey("SystemRoot"))
146 {
147 String os = System.getProperty("os.name");
148 if (os != null && os.toLowerCase(Locale.ENGLISH).indexOf("windows") != -1)
149 {
150 _env.set("SystemRoot","C:\\WINDOWS");
151 }
152 }
153
154 _ok = true;
155 }
156
157
158 @Override
159 public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
160 {
161 if (!_ok)
162 {
163 res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
164 return;
165 }
166
167 String pathInContext = (_relative?"":StringUtil.nonNull(req.getServletPath())) + StringUtil.nonNull(req.getPathInfo());
168 if (LOG.isDebugEnabled())
169 {
170 LOG.debug("CGI: ContextPath : " + req.getContextPath());
171 LOG.debug("CGI: ServletPath : " + req.getServletPath());
172 LOG.debug("CGI: PathInfo : " + req.getPathInfo());
173 LOG.debug("CGI: _docRoot : " + _docRoot);
174 LOG.debug("CGI: _path : " + _path);
175 LOG.debug("CGI: _ignoreExitState: " + _ignoreExitState);
176 }
177
178
179
180
181
182 String both = pathInContext;
183 String first = both;
184 String last = "";
185
186 File exe = new File(_docRoot,first);
187
188 while ((first.endsWith("/") || !exe.exists()) && first.length() >= 0)
189 {
190 int index = first.lastIndexOf('/');
191
192 first = first.substring(0,index);
193 last = both.substring(index,both.length());
194 exe = new File(_docRoot,first);
195 }
196
197 if (first.length() == 0 || !exe.exists() || exe.isDirectory() || !exe.getCanonicalPath().equals(exe.getAbsolutePath()))
198 {
199 res.sendError(404);
200 }
201 else
202 {
203 if (LOG.isDebugEnabled())
204 {
205 LOG.debug("CGI: script is " + exe);
206 LOG.debug("CGI: pathInfo is " + last);
207 }
208 exec(exe,last,req,res);
209 }
210 }
211
212
213
214
215
216 private void exec(File command, String pathInfo, HttpServletRequest req, HttpServletResponse res) throws IOException
217 {
218 String path = command.getAbsolutePath();
219 File dir = command.getParentFile();
220 String scriptName = req.getRequestURI().substring(0,req.getRequestURI().length() - pathInfo.length());
221 String scriptPath = getServletContext().getRealPath(scriptName);
222 String pathTranslated = req.getPathTranslated();
223
224 int len = req.getContentLength();
225 if (len < 0)
226 len = 0;
227 if ((pathTranslated == null) || (pathTranslated.length() == 0))
228 pathTranslated = path;
229
230 String bodyFormEncoded = null;
231 if ((HttpMethods.POST.equals(req.getMethod()) || HttpMethods.PUT.equals(req.getMethod())) && "application/x-www-form-urlencoded".equals(req.getContentType()))
232 {
233 MultiMap<String> parameterMap = new MultiMap<String>();
234 Enumeration names = req.getParameterNames();
235 while (names.hasMoreElements())
236 {
237 String parameterName = (String)names.nextElement();
238 parameterMap.addValues(parameterName, req.getParameterValues(parameterName));
239 }
240 bodyFormEncoded = UrlEncoded.encode(parameterMap, req.getCharacterEncoding(), true);
241 }
242
243 EnvList env = new EnvList(_env);
244
245
246
247 env.set("AUTH_TYPE", req.getAuthType());
248 if (bodyFormEncoded != null)
249 {
250 env.set("CONTENT_LENGTH", Integer.toString(bodyFormEncoded.length()));
251 }
252 else
253 {
254 env.set("CONTENT_LENGTH", Integer.toString(len));
255 }
256 env.set("CONTENT_TYPE", req.getContentType());
257 env.set("GATEWAY_INTERFACE", "CGI/1.1");
258 if ((pathInfo != null) && (pathInfo.length() > 0))
259 {
260 env.set("PATH_INFO", pathInfo);
261 }
262 env.set("PATH_TRANSLATED", pathTranslated);
263 env.set("QUERY_STRING", req.getQueryString());
264 env.set("REMOTE_ADDR", req.getRemoteAddr());
265 env.set("REMOTE_HOST", req.getRemoteHost());
266
267
268
269
270
271 env.set("REMOTE_USER", req.getRemoteUser());
272 env.set("REQUEST_METHOD", req.getMethod());
273 env.set("SCRIPT_NAME", scriptName);
274 env.set("SCRIPT_FILENAME", scriptPath);
275 env.set("SERVER_NAME", req.getServerName());
276 env.set("SERVER_PORT", Integer.toString(req.getServerPort()));
277 env.set("SERVER_PROTOCOL", req.getProtocol());
278 env.set("SERVER_SOFTWARE", getServletContext().getServerInfo());
279
280 Enumeration enm = req.getHeaderNames();
281 while (enm.hasMoreElements())
282 {
283 String name = (String)enm.nextElement();
284 String value = req.getHeader(name);
285 env.set("HTTP_" + name.toUpperCase(Locale.ENGLISH).replace('-','_'),value);
286 }
287
288
289 env.set("HTTPS", (req.isSecure()?"ON":"OFF"));
290
291
292
293
294
295
296
297 String execCmd = path;
298 if ((execCmd.charAt(0) != '"') && (execCmd.indexOf(" ") >= 0))
299 execCmd = "\"" + execCmd + "\"";
300 if (_cmdPrefix != null)
301 execCmd = _cmdPrefix + " " + execCmd;
302
303 LOG.debug("Environment: " + env.getExportString());
304 LOG.debug("Command: " + execCmd);
305
306 Process p;
307 if (dir == null)
308 p = Runtime.getRuntime().exec(execCmd, env.getEnvArray());
309 else
310 p = Runtime.getRuntime().exec(execCmd, env.getEnvArray(), dir);
311
312
313 if (bodyFormEncoded != null)
314 writeProcessInput(p, bodyFormEncoded);
315 else if (len > 0)
316 writeProcessInput(p, req.getInputStream(), len);
317
318 IO.copyThread(p.getErrorStream(), System.err);
319
320
321
322 OutputStream os = null;
323 try
324 {
325
326
327 String line = null;
328 InputStream inFromCgi = p.getInputStream();
329
330
331
332 while ((line = getTextLineFromStream(inFromCgi)).length() > 0)
333 {
334 if (!line.startsWith("HTTP"))
335 {
336 int k = line.indexOf(':');
337 if (k > 0)
338 {
339 String key = line.substring(0,k).trim();
340 String value = line.substring(k + 1).trim();
341 if ("Location".equals(key))
342 {
343 res.sendRedirect(res.encodeRedirectURL(value));
344 }
345 else if ("Status".equals(key))
346 {
347 String[] token = value.split(" ");
348 int status = Integer.parseInt(token[0]);
349 res.setStatus(status);
350 }
351 else
352 {
353
354 res.addHeader(key,value);
355 }
356 }
357 }
358 }
359
360 os = res.getOutputStream();
361 IO.copy(inFromCgi,os);
362 p.waitFor();
363
364 if (!_ignoreExitState)
365 {
366 int exitValue = p.exitValue();
367 if (0 != exitValue)
368 {
369 LOG.warn("Non-zero exit status (" + exitValue + ") from CGI program: " + path);
370 if (!res.isCommitted())
371 res.sendError(500,"Failed to exec CGI");
372 }
373 }
374 }
375 catch (IOException e)
376 {
377
378
379 LOG.debug("CGI: Client closed connection!");
380 }
381 catch (InterruptedException ie)
382 {
383 LOG.debug("CGI: interrupted!");
384 }
385 finally
386 {
387 if (os != null)
388 {
389 try
390 {
391 os.close();
392 }
393 catch (Exception e)
394 {
395 LOG.debug(e);
396 }
397 }
398 p.destroy();
399
400 }
401 }
402
403 private static void writeProcessInput(final Process p, final String input)
404 {
405 new Thread(new Runnable()
406 {
407 public void run()
408 {
409 try
410 {
411 Writer outToCgi = new OutputStreamWriter(p.getOutputStream());
412 outToCgi.write(input);
413 outToCgi.close();
414 }
415 catch (IOException e)
416 {
417 LOG.debug(e);
418 }
419 }
420 }).start();
421 }
422
423 private static void writeProcessInput(final Process p, final InputStream input, final int len)
424 {
425 if (len <= 0) return;
426
427 new Thread(new Runnable()
428 {
429 public void run()
430 {
431 try
432 {
433 OutputStream outToCgi = p.getOutputStream();
434 IO.copy(input, outToCgi, len);
435 outToCgi.close();
436 }
437 catch (IOException e)
438 {
439 LOG.debug(e);
440 }
441 }
442 }).start();
443 }
444
445
446
447
448
449
450
451
452
453 private static String getTextLineFromStream(InputStream is) throws IOException
454 {
455 StringBuilder buffer = new StringBuilder();
456 int b;
457
458 while ((b = is.read()) != -1 && b != '\n')
459 {
460 buffer.append((char)b);
461 }
462 return buffer.toString().trim();
463 }
464
465
466
467
468
469 private static class EnvList
470 {
471 private Map<String, String> envMap;
472
473 EnvList()
474 {
475 envMap = new HashMap<String, String>();
476 }
477
478 EnvList(EnvList l)
479 {
480 envMap = new HashMap<String,String>(l.envMap);
481 }
482
483
484
485
486 public void set(String name, String value)
487 {
488 envMap.put(name,name + "=" + StringUtil.nonNull(value));
489 }
490
491
492 public String[] getEnvArray()
493 {
494 return envMap.values().toArray(new String[envMap.size()]);
495 }
496
497 public String getExportString()
498 {
499 StringBuilder sb = new StringBuilder();
500 for (String variable : getEnvArray())
501 {
502 sb.append("export \"");
503 sb.append(variable);
504 sb.append("\"; ");
505 }
506 return sb.toString();
507 }
508
509 @Override
510 public String toString()
511 {
512 return envMap.toString();
513 }
514 }
515 }