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