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