Improved CGI capabilities:

*) CGI respects shebang now (should solve problems with MS Windows)
*) better error handling (more correct HTTP error codes)
*) logging

git-svn-id: https://svn.berlios.de/svnroot/repos/yacy/trunk@8136 6c8d7289-2bf4-0310-a012-ef5d649a1542
pull/1/head
low012 13 years ago
parent 402e9d71ef
commit 7cfdc2c092

@ -61,10 +61,12 @@ import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -74,6 +76,8 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
@ -646,143 +650,227 @@ public final class HTTPDFileHandler {
}
}
}
} else if (((switchboard.getConfigBool("cgi.allow", false)) && // check if CGI execution is allowed in config
(matchesSuffix(path, switchboard.getConfig("cgi.suffixes", null))) && // "right" file extension?
(path.substring(0, path.indexOf(targetFile.getName())).contains("/CGI-BIN/") ||
path.substring(0, path.indexOf(targetFile.getName())).contains("/cgi-bin/")) && // file in right directory?
targetFile.exists())
// old-school CGI execution
} else if ((switchboard.getConfigBool("cgi.allow", false) // check if CGI execution is allowed in config
&& matchesSuffix(path, switchboard.getConfig("cgi.suffixes", null)) // "right" file extension?
&& path.substring(0, path.indexOf(targetFile.getName())).toUpperCase().contains("/CGI-BIN/") // file in right directory?
&& targetFile.exists())
) {
String mimeType = "text/html";
int statusCode = 200;
if (!targetFile.canExecute()) {
HTTPDemon.sendRespondError(
conProp,
out,
-1,
403,
null,
HeaderFramework.http1_1.get(
Integer.toString(403)),
null);
Log.logWarning(
"HTTPD",
"CGI script " + targetFile.getPath()
+ " could not be executed due to "
+ "insufficient access rights.");
} else {
String mimeType = "text/html";
int statusCode = 200;
ProcessBuilder pb;
final ProcessBuilder pb =
new ProcessBuilder(assembleCommandFromShebang(targetFile));
pb.directory(targetFile.getParentFile());
final String fileSeparator =
System.getProperty("file.separator", "/");
// set environment variables
final Map<String, String> env = pb.environment();
env.put(
"SERVER_SOFTWARE",
getDefaultHeaders(path).get(HeaderFramework.SERVER));
env.put("SERVER_NAME", sb.peers.mySeed().getName());
env.put("GATEWAY_INTERFACE", "CGI/1.1");
if (httpVersion != null) {
env.put("SERVER_PROTOCOL", httpVersion);
}
env.put("SERVER_PORT", switchboard.getConfig("port", "8090"));
env.put("REQUEST_METHOD", method);
// env.put("PATH_INFO", ""); // TODO: implement
// env.put("PATH_TRANSLATED", ""); // TODO: implement
env.put("SCRIPT_NAME", path);
if (argsString != null) {
env.put("QUERY_STRING", argsString);
}
env.put("REMOTE_ADDR", clientIP);
// env.put("AUTH_TYPE", ""); // TODO: implement
// env.put("REMOTE_USER", ""); // TODO: implement
// env.put("REMOTE_IDENT", ""); // I don't think we need this
env.put(
"DOCUMENT_ROOT",
switchboard.getAppPath().getAbsolutePath()
+ fileSeparator + switchboard.getConfig("htDocsPath", "DATA/HTDOCS"));
if (requestHeader.getContentType() != null) {
env.put("CONTENT_TYPE", requestHeader.getContentType());
}
if (method.equalsIgnoreCase(HeaderFramework.METHOD_POST)
&& body != null) {
env.put(
"CONTENT_LENGTH",
Integer.toString(requestHeader.getContentLength()));
}
pb = new ProcessBuilder(targetFile.getAbsolutePath());
/* add values from request header to environment
* (see: http://hoohoo.ncsa.uiuc.edu/cgi/env.html#headers) */
for (final Map.Entry<String, String> requestHeaderEntry
: requestHeader.entrySet()) {
env.put("HTTP_"
+ requestHeaderEntry.getKey().toUpperCase().replace("-", "_"),
requestHeaderEntry.getValue());
}
final String fileSeparator = System.getProperty("file.separator", "/");
int exitValue = 0;
String cgiBody = null;
final StringBuilder error = new StringBuilder(256);
// set environment variables
final Map<String, String> env = pb.environment();
env.put("SERVER_SOFTWARE", getDefaultHeaders(path).get(HeaderFramework.SERVER));
env.put("SERVER_NAME", sb.peers.mySeed().getName());
env.put("GATEWAY_INTERFACE", "CGI/1.1");
if (httpVersion != null) {
env.put("SERVER_PROTOCOL", httpVersion);
}
env.put("SERVER_PORT", switchboard.getConfig("port", "8090"));
env.put("REQUEST_METHOD", method);
// env.put("PATH_INFO", ""); // TODO: implement
// env.put("PATH_TRANSLATED", ""); // TODO: implement
env.put("SCRIPT_NAME", path);
if (argsString != null) {
env.put("QUERY_STRING", argsString);
}
env.put("REMOTE_ADDR", clientIP);
// env.put("AUTH_TYPE", ""); // TODO: implement
// env.put("REMOTE_USER", ""); // TODO: implement
// env.put("REMOTE_IDENT", ""); // I don't think we need this
env.put("DOCUMENT_ROOT", switchboard.getAppPath().getAbsolutePath() + fileSeparator + switchboard.getConfig("htDocsPath", "DATA/HTDOCS"));
if (requestHeader.getContentType() != null) {
env.put("CONTENT_TYPE", requestHeader.getContentType());
}
if (method.equalsIgnoreCase(HeaderFramework.METHOD_POST) && body != null) {
env.put("CONTENT_LENGTH", Integer.toString(requestHeader.getContentLength()));
}
try {
// start execution of script
final Process p = pb.start();
final OutputStream os =
new BufferedOutputStream(p.getOutputStream());
if (method.equalsIgnoreCase(
HeaderFramework.METHOD_POST) && body != null) {
final byte[] buffer = new byte[1024];
int len = requestHeader.getContentLength();
while (len > 0) {
body.read(buffer);
len = len - buffer.length;
os.write(buffer);
}
}
// add values from request header to environment (see: http://hoohoo.ncsa.uiuc.edu/cgi/env.html#headers)
for (final Map.Entry<String, String> requestHeaderEntry : requestHeader.entrySet()) {
env.put("HTTP_" + requestHeaderEntry.getKey().toUpperCase().replace("-", "_"), requestHeaderEntry.getValue());
}
os.close();
int exitValue = 0;
String cgiBody = null;
try {
p.waitFor();
} catch (final InterruptedException ex) {
try {
// start execution of script
final Process p = pb.start();
final OutputStream os = new BufferedOutputStream(p.getOutputStream());
if (method.equalsIgnoreCase(HeaderFramework.METHOD_POST) && body != null) {
final byte[] buffer = new byte[1024];
int len = requestHeader.getContentLength();
while (len > 0) {
body.read(buffer);
len = len - buffer.length;
os.write(buffer);
}
}
os.close();
exitValue = p.exitValue();
try {
p.waitFor();
} catch (final InterruptedException ex) {
}
final InputStream is =
new BufferedInputStream(p.getInputStream());
exitValue = p.exitValue();
final InputStream es =
new BufferedInputStream(p.getErrorStream());
final InputStream is = new BufferedInputStream(p.getInputStream());
final StringBuilder processOutput =
new StringBuilder(1024);
final StringBuilder StringBuilder = new StringBuilder(1024);
while (is.available() > 0) {
processOutput.append((char) is.read());
}
while (is.available() > 0) {
StringBuilder.append((char) is.read());
}
while (es.available() > 0) {
error.append((char) es.read());
}
final String cgiReturn = StringBuilder.toString();
int indexOfDelimiter = cgiReturn.indexOf("\n\n",0);
String[] cgiHeader = new String[0];
if (indexOfDelimiter > -1) {
cgiHeader = cgiReturn.substring(0, indexOfDelimiter).split("\n");
}
cgiBody = cgiReturn.substring(indexOfDelimiter + 1);
String key;
String value;
for (final String element : cgiHeader) {
indexOfDelimiter = element.indexOf(':');
key = element.substring(0, indexOfDelimiter).trim();
value = element.substring(indexOfDelimiter + 1).trim();
conProp.put(key, value);
if (key.equals("Cache-Control") && value.equals("no-cache")) {
nocache = true;
} else if (key.equals("Content-type")) {
mimeType = value;
} else if (key.equals("Status")) {
if (key.length() > 2) {
try {
statusCode = Integer.parseInt(value.substring(0, 3));
} catch (final NumberFormatException ex) {
/* tough luck, we will just have to use 200 as default value */
int indexOfDelimiter = processOutput.indexOf("\n\n", 0);
final String[] cgiHeader;
if (indexOfDelimiter > -1) {
cgiHeader =
processOutput.substring(
0, indexOfDelimiter).split("\n");
} else {
cgiHeader = new String[0];
}
cgiBody = processOutput.substring(indexOfDelimiter + 1);
String key;
String value;
for (final String element : cgiHeader) {
indexOfDelimiter = element.indexOf(':');
key = element.substring(0, indexOfDelimiter).trim();
value = element.substring(indexOfDelimiter + 1).trim();
conProp.put(key, value);
if ("Cache-Control".equals(key)
&& "no-cache".equals(value)) {
nocache = true;
} else if ("Content-type".equals(key)) {
mimeType = value;
} else if ("Status".equals(key)) {
if (key.length() > 2) {
try {
statusCode =
Integer.parseInt(
value.substring(0, 3));
} catch (final NumberFormatException ex) {
Log.logWarning(
"HTTPD",
"CGI script " + targetFile.getPath()
+ " returned illegal status code \""
+ value + "\".");
}
}
}
}
} catch (final IOException ex) {
exitValue = -1;
}
} catch (final IOException ex) {
exitValue = -1;
}
/* did the script return an exit value != 0 and still there is supposed to be
* everything right with the HTTP status? -> change status to 500 since 200 would
* be a lie
*/
if (exitValue != 0 && statusCode == 200) {
statusCode = 500;
}
targetDate = new Date(System.currentTimeMillis());
/* did the script return an exit value != 0
* and still there is supposed to be
* everything right with the HTTP status?
* -> change status to 500 since 200 would
* be a lie
*/
if (exitValue != 0 && statusCode == 200) {
statusCode = 500;
}
if (exitValue == 0 || (cgiBody != null && !cgiBody.equals(""))) {
HTTPDemon.sendRespondHeader(conProp, out, httpVersion, statusCode, null, mimeType, cgiBody.length(), targetDate, null, null, null, null, nocache);
out.write(UTF8.getBytes(cgiBody));
} else {
HTTPDemon.sendRespondError(conProp, out, exitValue, statusCode, null, HeaderFramework.http1_1.get(Integer.toString(statusCode)), null);
targetDate = new Date(System.currentTimeMillis());
if (exitValue == 0
&& cgiBody != null
&& !cgiBody.isEmpty()) {
HTTPDemon.sendRespondHeader(
conProp,
out,
httpVersion,
statusCode,
null,
mimeType,
cgiBody.length(),
targetDate,
null,
null,
null,
null,
nocache);
out.write(UTF8.getBytes(cgiBody));
} else {
HTTPDemon.sendRespondError(
conProp,
out,
exitValue,
statusCode,
null,
HeaderFramework.http1_1.get(
Integer.toString(statusCode)),
null);
Log.logWarning(
"HTTPD",
"CGI script " + targetFile.getPath()
+ " returned exit value " + exitValue
+ ", body empty: "
+ (cgiBody == null || cgiBody.isEmpty()));
if (error.length() > 0) {
Log.logWarning("HTTPD", "Reported error: " + error);
}
}
}
} else if ((targetClass != null) && (path.endsWith(".stream"))) {
// call rewrite-class
requestHeader.put(HeaderFramework.CONNECTION_PROP_CLIENTIP, (String) conProp.get(HeaderFramework.CONNECTION_PROP_CLIENTIP));
@ -1177,6 +1265,29 @@ public final class HTTPDFileHandler {
}
}
/**
* Returns a list which contains parts of command
* which is used to start external process for
* CGI scripts.
* @param targetFile file to run
* @return list of parts of command
* @throws IOException if file can not be accessed
*/
private static List<String> assembleCommandFromShebang(
final File targetFile)
throws IOException {
final List<String > ret = new ArrayList<String>();
final BufferedReader br =
new BufferedReader(new FileReader(targetFile), 512);
final String line = br.readLine();
if (line.startsWith("#!")) {
ret.addAll(Arrays.asList(line.substring(2).split(" ")));
}
ret.add(targetFile.getAbsolutePath());
return ret;
}
private static final String appendPath(final String proplist, final String path) {
if (proplist.length() == 0) return path;
if (proplist.indexOf(path) >= 0) return proplist;

Loading…
Cancel
Save