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