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
88 @Override
89 public void init() throws ServletException
90 {
91 _env = new EnvList();
92 _cmdPrefix = getInitParameter("commandPrefix");
93 _useFullPath = Boolean.parseBoolean(getInitParameter("useFullPath"));
94 _relative = Boolean.parseBoolean(getInitParameter("cgibinResourceBaseIsRelative"));
95
96 String tmp = getInitParameter("cgibinResourceBase");
97 if (tmp != null)
98 _cgiBinProvided = true;
99 else
100 {
101 tmp = getInitParameter("resourceBase");
102 if (tmp != null)
103 _cgiBinProvided = true;
104 else
105 tmp = getServletContext().getRealPath("/");
106 }
107
108 if (_relative && _cgiBinProvided)
109 {
110 tmp = getServletContext().getRealPath(tmp);
111 }
112
113 if (tmp == null)
114 {
115 LOG.warn("CGI: no CGI bin !");
116 return;
117 }
118
119 File dir = new File(tmp);
120 if (!dir.exists())
121 {
122 LOG.warn("CGI: CGI bin does not exist - " + dir);
123 return;
124 }
125
126 if (!dir.canRead())
127 {
128 LOG.warn("CGI: CGI bin is not readable - " + dir);
129 return;
130 }
131
132 if (!dir.isDirectory())
133 {
134 LOG.warn("CGI: CGI bin is not a directory - " + dir);
135 return;
136 }
137
138 try
139 {
140 _docRoot = dir.getCanonicalFile();
141 }
142 catch (IOException e)
143 {
144 LOG.warn("CGI: CGI bin failed - " + dir,e);
145 return;
146 }
147
148 _path = getInitParameter("Path");
149 if (_path != null)
150 _env.set("PATH",_path);
151
152 _ignoreExitState = "true".equalsIgnoreCase(getInitParameter("ignoreExitState"));
153 Enumeration<String> e = getInitParameterNames();
154 while (e.hasMoreElements())
155 {
156 String n = e.nextElement();
157 if (n != null && n.startsWith("ENV_"))
158 _env.set(n.substring(4),getInitParameter(n));
159 }
160 if (!_env.envMap.containsKey("SystemRoot"))
161 {
162 String os = System.getProperty("os.name");
163 if (os != null && os.toLowerCase(Locale.ENGLISH).indexOf("windows") != -1)
164 {
165 _env.set("SystemRoot","C:\\WINDOWS");
166 }
167 }
168
169 _ok = true;
170 }
171
172
173 @Override
174 public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
175 {
176 if (!_ok)
177 {
178 res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
179 return;
180 }
181
182 if (LOG.isDebugEnabled())
183 {
184 LOG.debug("CGI: ContextPath : " + req.getContextPath());
185 LOG.debug("CGI: ServletPath : " + req.getServletPath());
186 LOG.debug("CGI: PathInfo : " + req.getPathInfo());
187 LOG.debug("CGI: _docRoot : " + _docRoot);
188 LOG.debug("CGI: _path : " + _path);
189 LOG.debug("CGI: _ignoreExitState: " + _ignoreExitState);
190 }
191
192
193
194
195 String pathInContext = (_relative ? "" : StringUtil.nonNull(req.getServletPath())) + StringUtil.nonNull(req.getPathInfo());
196 File execCmd = new File(_docRoot, pathInContext);
197 String pathInfo = pathInContext;
198
199 if(!_useFullPath)
200 {
201 String path = pathInContext;
202 String info = "";
203
204
205 while ((path.endsWith("/") || !execCmd.exists()) && path.length() >= 0)
206 {
207 int index = path.lastIndexOf('/');
208 path = path.substring(0,index);
209 info = pathInContext.substring(index,pathInContext.length());
210 execCmd = new File(_docRoot,path);
211 }
212
213 if (path.length() == 0 || !execCmd.exists() || execCmd.isDirectory() || !execCmd.getCanonicalPath().equals(execCmd.getAbsolutePath()))
214 {
215 res.sendError(404);
216 }
217
218 pathInfo = info;
219 }
220 exec(execCmd,pathInfo,req,res);
221 }
222
223
224
225
226
227
228
229
230
231
232
233 private void exec(File command, String pathInfo, HttpServletRequest req, HttpServletResponse res) throws IOException
234 {
235 assert req != null;
236 assert res != null;
237 assert pathInfo != null;
238 assert command != null;
239
240 if (LOG.isDebugEnabled())
241 {
242 LOG.debug("CGI: script is " + command);
243 LOG.debug("CGI: pathInfo is " + pathInfo);
244 }
245
246 String bodyFormEncoded = null;
247 if ((HttpMethod.POST.equals(req.getMethod()) || HttpMethod.PUT.equals(req.getMethod())) && "application/x-www-form-urlencoded".equals(req.getContentType()))
248 {
249 MultiMap<String> parameterMap = new MultiMap<String>();
250 Enumeration<String> names = req.getParameterNames();
251 while (names.hasMoreElements())
252 {
253 String parameterName = names.nextElement();
254 parameterMap.addValues(parameterName, req.getParameterValues(parameterName));
255 }
256 bodyFormEncoded = UrlEncoded.encode(parameterMap, Charset.forName(req.getCharacterEncoding()), true);
257 }
258
259 EnvList env = new EnvList(_env);
260
261
262
263 env.set("AUTH_TYPE", req.getAuthType());
264
265 int contentLen = req.getContentLength();
266 if (contentLen < 0)
267 contentLen = 0;
268 if (bodyFormEncoded != null)
269 {
270 env.set("CONTENT_LENGTH", Integer.toString(bodyFormEncoded.length()));
271 }
272 else
273 {
274 env.set("CONTENT_LENGTH", Integer.toString(contentLen));
275 }
276 env.set("CONTENT_TYPE", req.getContentType());
277 env.set("GATEWAY_INTERFACE", "CGI/1.1");
278 if (pathInfo.length() > 0)
279 {
280 env.set("PATH_INFO", pathInfo);
281 }
282
283 String pathTranslated = req.getPathTranslated();
284 if ((pathTranslated == null) || (pathTranslated.length() == 0))
285 pathTranslated = pathInfo;
286 env.set("PATH_TRANSLATED", pathTranslated);
287 env.set("QUERY_STRING", req.getQueryString());
288 env.set("REMOTE_ADDR", req.getRemoteAddr());
289 env.set("REMOTE_HOST", req.getRemoteHost());
290
291
292
293
294
295
296 env.set("REMOTE_USER", req.getRemoteUser());
297 env.set("REQUEST_METHOD", req.getMethod());
298
299 String scriptPath;
300 String scriptName;
301
302 if(_cgiBinProvided)
303 {
304 scriptPath = command.getAbsolutePath();
305 scriptName = scriptPath.substring(_docRoot.getAbsolutePath().length());
306 }
307 else
308 {
309 String requestURI = req.getRequestURI();
310 scriptName = requestURI.substring(0,requestURI.length() - pathInfo.length());
311 scriptPath = getServletContext().getRealPath(scriptName);
312 }
313 env.set("SCRIPT_FILENAME", scriptPath);
314 env.set("SCRIPT_NAME", scriptName);
315
316 env.set("SERVER_NAME", req.getServerName());
317 env.set("SERVER_PORT", Integer.toString(req.getServerPort()));
318 env.set("SERVER_PROTOCOL", req.getProtocol());
319 env.set("SERVER_SOFTWARE", getServletContext().getServerInfo());
320
321 Enumeration<String> enm = req.getHeaderNames();
322 while (enm.hasMoreElements())
323 {
324 String name = enm.nextElement();
325 String value = req.getHeader(name);
326 env.set("HTTP_" + name.toUpperCase(Locale.ENGLISH).replace('-','_'),value);
327 }
328
329
330 env.set("HTTPS", (req.isSecure()?"ON":"OFF"));
331
332
333
334
335
336
337
338 String absolutePath = command.getAbsolutePath();
339 String execCmd = absolutePath;
340
341
342 if (execCmd.length() > 0 && execCmd.charAt(0) != '"' && execCmd.indexOf(" ") >= 0)
343 execCmd = "\"" + execCmd + "\"";
344
345 if (_cmdPrefix != null)
346 execCmd = _cmdPrefix + " " + execCmd;
347
348 assert execCmd != null;
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
514 private static String getTextLineFromStream(InputStream is) throws IOException
515 {
516 StringBuilder buffer = new StringBuilder();
517 int b;
518
519 while ((b = is.read()) != -1 && b != '\n')
520 {
521 buffer.append((char)b);
522 }
523 return buffer.toString().trim();
524 }
525
526
527
528
529
530 private static class EnvList
531 {
532 private Map<String, String> envMap;
533
534 EnvList()
535 {
536 envMap = new HashMap<String, String>();
537 }
538
539 EnvList(EnvList l)
540 {
541 envMap = new HashMap<String,String>(l.envMap);
542 }
543
544
545
546
547
548
549 public void set(String name, String value)
550 {
551 envMap.put(name,name + "=" + StringUtil.nonNull(value));
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 }