Skip to content

Commit

Permalink
*) added CGI capabilities (run Perl scripts and other software via HT…
Browse files Browse the repository at this point in the history
…TP GET and POST)

*) set cgi.allow to true in yacy.conf to enable CGI (CGI is disabled by default)
*) edit cgi.suffixes in yacy.conf if necessary to use additional script types

ATTENTION: This is a rather experimental feature, not all environment variables are set yet. 

Only enable CGI if you know what you are doing. Poorly implemented CGI scripts can put a system's integrity at risk!

Implementation of more environment variables and documentation due for the next days.

git-svn-id: https://svn.berlios.de/svnroot/repos/yacy/trunk@5428 6c8d7289-2bf4-0310-a012-ef5d649a1542
  • Loading branch information
low012 committed Jan 1, 2009
1 parent bdc380c commit f547f9a
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 8 deletions.
4 changes: 4 additions & 0 deletions defaults/yacy.init
Expand Up @@ -875,3 +875,7 @@ compare_yacy.right = YaCy

# minimum free disk space for crawling (MiB)
disk.free = 3000

# setting if execution of CGI files is allowed or not
cgi.allow = false
cgi.suffixes = cgi,pl
3 changes: 2 additions & 1 deletion source/de/anomic/http/httpd.java
Expand Up @@ -80,7 +80,7 @@

/**
* Instances of this class can be passed as argument to the serverCore.
* The generic server dispatches HTTP commands and calls the
* The generic server dispatches HTTP commands and calls the serverObjects
* method GET, HEAD or POST in this class
* these methods parse the command line and decide wether to call
* a proxy servlet or a file server servlet
Expand Down Expand Up @@ -1031,6 +1031,7 @@ public static boolean equals(final byte[] a, final int aoff, final byte[] b, fin
return true;
}

@Override
public httpd clone() {
return new httpd(switchboard);
}
Expand Down
194 changes: 187 additions & 7 deletions source/de/anomic/http/httpdFileHandler.java
Expand Up @@ -3,7 +3,9 @@
// (C) by Michael Peter Christen; mc@yacy.net
// first published on http://www.anomic.de
// Frankfurt, Germany, 2004, 2005
// last major change: 05.10.2005
// last major change: 01.01.2009
//
// CGI additions by Marc Nause
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -57,6 +59,7 @@ public static java.util.Hashtable respond(java.util.HashMap, serverSwitch)
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
Expand All @@ -73,6 +76,7 @@ public static java.util.Hashtable respond(java.util.HashMap, serverSwitch)
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.zip.GZIPOutputStream;

import de.anomic.htmlFilter.htmlFilterContentScraper;
Expand Down Expand Up @@ -193,7 +197,7 @@ public static File getLocalizedFile(final String path){
return getLocalizedFile(path, switchboard.getConfig("locale.language","default"));
}

/** Returns a path to the localized or default file according to the parameter localeSelection
/** Returns a path to the localized or default file according to the parameter localeSelectionhttp://localhost:8080/
* @param path relative from htroot
* @param localeSelection language of localized file; locale.language from switchboard is used if localeSelection.equals("") */
public static File getLocalizedFile(final String path, final String localeSelection){
Expand Down Expand Up @@ -256,7 +260,7 @@ public static void doPost(final Properties conProp, final httpRequestHeader requ
}

public static void doResponse(final Properties conProp, final httpRequestHeader requestHeader, final OutputStream out, final InputStream body) {

String path = null;
try {
// getting some connection properties
Expand All @@ -280,7 +284,7 @@ public static void doResponse(final Properties conProp, final httpRequestHeader
assert(false) : "UnsupportedEncodingException: " + e.getMessage();
}

// check again hack attacks in path
// check against hack attacks in path
if (path.indexOf("..") >= 0) {
httpd.sendRespondError(conProp,out,4,403,null,"Access not allowed",null);
return;
Expand Down Expand Up @@ -340,7 +344,14 @@ public static void doResponse(final Properties conProp, final httpRequestHeader
// no args here, maybe a POST with multipart extension
int length = requestHeader.getContentLength();
//System.out.println("HEADER: " + requestHeader.toString()); // DEBUG
if (method.equals(httpHeader.METHOD_POST)) {

/* don't parse body in case of a POST CGI call since it has to be
* handed over to the CGI script unaltered and parsed by the script
*/
if (method.equals(httpHeader.METHOD_POST) &&
!(switchboard.getConfigBool("cgi.allow", false) &&
httpdFileHandler.matchesSuffix(path, switchboard.getConfig("cgi.suffixes", null)))
) {

// if its a POST, it can be either multipart or as args in the body
if ((requestHeader.containsKey(httpHeader.CONTENT_TYPE)) &&
Expand Down Expand Up @@ -410,7 +421,7 @@ public static void doResponse(final Properties conProp, final httpRequestHeader
localeSelection = localeSelection.substring(0, localeSelection.indexOf("."));
}

File targetFile = getLocalizedFile(path, localeSelection);
File targetFile = getLocalizedFile(path, localeSelection);
final String targetExt = conProp.getProperty("EXT","");
targetClass = rewriteClassFile(new File(htDefaultPath, path));
if (path.endsWith("/")) {
Expand Down Expand Up @@ -485,7 +496,11 @@ public static void doResponse(final Properties conProp, final httpRequestHeader
}
} else {
//XXX: you cannot share a .png/.gif file with a name like a class in htroot.
if ( !(targetFile.exists()) && !((path.endsWith("png")||path.endsWith("gif")||path.endsWith(".stream"))&&targetClass!=null ) ){
if ( !(targetFile.exists()) &&
!((path.endsWith("png")||path.endsWith("gif") ||
matchesSuffix(path, switchboard.getConfig("cgi.suffixes", null)) ||
path.endsWith(".stream")) &&
targetClass!=null ) ){
targetFile = new File(htDocsPath, path);
targetClass = rewriteClassFile(new File(htDocsPath, path));
}
Expand Down Expand Up @@ -553,6 +568,148 @@ public static void doResponse(final Properties conProp, final httpRequestHeader
}
}
}
} else if (((switchboard.getConfigBool("cgi.allow", false)) && // check if CGI execution is allowed in config
(httpdFileHandler.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())
) {

String mimeType = "text/html";
int statusCode = 200;

boolean argToCommandline = false;
// see http://hoohoo.ncsa.uiuc.edu/cgi/cl.html)
if (argsString != null && !argsString.contains("=")) {
argToCommandline = true;
}

ProcessBuilder pb;

if (argToCommandline) {
pb = new ProcessBuilder(targetFile.getAbsolutePath(), argsString);
} else {
pb = new ProcessBuilder(targetFile.getAbsolutePath());
}

// set environment variables
Map<String, String> env = pb.environment();
env.put("SERVER_SOFTWARE", getDefaultHeaders(path).get(httpResponseHeader.SERVER));
env.put("SERVER_NAME", switchboard.getConfig("peerName", "<nameless>"));
env.put("GATEWAY_INTERFACE", "CGI/1.1");
if (httpVersion != null) {
env.put("SERVER_PROTOCOL", httpVersion);
}
env.put("SERVER_PORT", switchboard.getConfig("port", "8080"));
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_HOST", refererHost);
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
if (requestHeader.getContentType() != null) {
env.put("CONTENT_TYPE", requestHeader.getContentType());
}
if (method.equalsIgnoreCase(httpHeader.METHOD_POST) && body != null) {
env.put("CONTENT_LENGTH", requestHeader.getContentLength() + "");
}

// add values from request header to environment (see: http://hoohoo.ncsa.uiuc.edu/cgi/env.html#headers)
Set<String> requestHeaderKeys = requestHeader.keySet();
for (String requestHeaderKey : requestHeaderKeys) {
env.put("HTTP_" + requestHeaderKey.toUpperCase().replace("-", "_"), requestHeader.get(requestHeaderKey));
}

// TODO: set directory path for CGI script if this is necessary at all
//pb.directory(new File(targetFile.getAbsolutePath().substring(0, path.lastIndexOf("/"))));

// start execution of script
Process p = pb.start();

OutputStream os = new BufferedOutputStream(p.getOutputStream());

if (!argToCommandline && method.equalsIgnoreCase(httpHeader.METHOD_POST) && body != null) {
byte[] buffer = new byte[1024];
int len = requestHeader.getContentLength();
while (len > 0) {
body.read(buffer);
len = len - buffer.length;
os.write(buffer);
}
}

os.close();

try {
p.waitFor();
} catch (InterruptedException ex) {

}

int exitValue = p.exitValue();

InputStream is = new BufferedInputStream(p.getInputStream());

StringBuilder stringBuffer = new StringBuilder(1024);

while (is.available() > 0) {
stringBuffer.append((char) is.read());
}

String cgiReturn = stringBuffer.toString();
int indexOfDelimiter = cgiReturn.indexOf("\n\n");
String[] cgiHeader = new String[0];
if (indexOfDelimiter > -1) {
cgiHeader = cgiReturn.substring(0, indexOfDelimiter).split("\n");
}
String cgiBody = cgiReturn.substring(indexOfDelimiter + 1);

String key;
String value;
for (int i = 0; i < cgiHeader.length; i++) {
indexOfDelimiter = cgiHeader[i].indexOf(":");
key = cgiHeader[i].substring(0, indexOfDelimiter).trim();
value = cgiHeader[i].substring(indexOfDelimiter + 1).trim();
conProp.setProperty(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 (NumberFormatException ex) {
/* tough luck, we will just have to use 200 as default value */
}
}
}
}

/* 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());

if (exitValue == 0 || (cgiBody != null && !cgiBody.equals(""))) {
httpd.sendRespondHeader(conProp, out, httpVersion, statusCode, null, mimeType, cgiBody.length(), targetDate, null, null, null, null, nocache);
out.write(cgiBody.getBytes());
} else {
httpd.sendRespondError(conProp, out, exitValue, statusCode, null, httpHeader.http1_1.get(statusCode + ""), null);
}


} else if ((targetClass != null) && (path.endsWith(".stream"))) {
// call rewrite-class
requestHeader.put(httpHeader.CONNECTION_PROP_CLIENTIP, conProp.getProperty(httpHeader.CONNECTION_PROP_CLIENTIP));
Expand Down Expand Up @@ -1030,5 +1187,28 @@ public static final Object invokeServlet(final File targetClass, final httpReque
// public void doConnect(Properties conProp, httpHeader requestHeader, InputStream clientIn, OutputStream clientOut) {
// throw new UnsupportedOperationException();
// }

/**
* Tells if a filename ends with a suffix from a given list.
* @param filename the filename
* @param suffixList the list of suffixes which is a string of suffixes separated by commas
* @return true if the filename ends with a suffix from the list, else false
*/
private static boolean matchesSuffix(String name, String suffixList) {
boolean ret = false;

if (suffixList != null && name != null) {
String[] suffixes = suffixList.split(",");
find:
for (int i = 0; i < suffixes.length; i++) {
if (name.endsWith("." + suffixes[i].trim())) {
ret = true;
break find;
}
}
}

return ret;
}

}

0 comments on commit f547f9a

Please sign in to comment.