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