yacy_search_server/source/de/anomic/http/httpd.java

1389 lines
68 KiB
Java

// httpd.java
// -----------------------
// (C) by Michael Peter Christen; mc@anomic.de
// first published on http://www.anomic.de
// Frankfurt, Germany, 2004
//
// last major change: $LastChangedDate$ by $LastChangedBy$
// Revision: $LastChangedRevision$
//
// 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
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Using this software in any meaning (reading, learning, copying, compiling,
// running) means that you agree that the Author(s) is (are) not responsible
// for cost, loss of data or any harm that may be caused directly or indirectly
// by usage of this softare or this documentation. The usage of this software
// is on your own risk. The installation and usage (starting/running) of this
// software may allow other people or application to access your computer and
// any attached devices and is highly dependent on the configuration of the
// software which must be done by the user of the software; the author(s) is
// (are) also not responsible for proper configuration and usage of the
// software, even if provoked by documentation provided together with
// the software.
//
// Any changes to this file according to the GPL as documented in the file
// gpl.txt aside this file in the shipment you received can be done to the
// lines that follows this copyright notice here, but changes must not be
// done inside the copyright notive above. A re-distribution must contain
// the intact and unchanged copyright notice.
// Contributions and changes to the program code must be marked as such.
package de.anomic.http;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PushbackInputStream;
import java.lang.reflect.Constructor;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.StringTokenizer;
import de.anomic.plasma.plasmaSwitchboard;
import de.anomic.server.serverByteBuffer;
import de.anomic.server.serverCodings;
import de.anomic.server.serverCore;
import de.anomic.server.serverFileUtils;
import de.anomic.server.serverHandler;
import de.anomic.server.serverObjects;
import de.anomic.server.serverSwitch;
import de.anomic.server.logging.serverLog;
import de.anomic.yacy.yacyCore;
import de.anomic.yacy.yacyDHTAction;
import de.anomic.yacy.yacySeed;
/**
* Instances of this class can be passed as argument to the serverCore.
* The generic server dispatches HTTP commands and calls the
* 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
*/
public final class httpd implements serverHandler {
/* PROPERTIES: General properties */
public static final String CONNECTION_PROP_HTTP_VER = "HTTP";
public static final String CONNECTION_PROP_HOST = "HOST";
public static final String CONNECTION_PROP_METHOD = "METHOD";
public static final String CONNECTION_PROP_PATH = "PATH";
public static final String CONNECTION_PROP_EXT = "EXT";
public static final String CONNECTION_PROP_URL = "URL";
public static final String CONNECTION_PROP_ARGS = "ARGS";
public static final String CONNECTION_PROP_CLIENTIP = "CLIENTIP";
public static final String CONNECTION_PROP_PERSISTENT = "PERSISTENT";
public static final String CONNECTION_PROP_KEEP_ALIVE_COUNT = "KEEP-ALIVE_COUNT";
public static final String CONNECTION_PROP_REQUESTLINE = "REQUESTLINE";
public static final String CONNECTION_PROP_REQUEST_START = "REQUEST_START";
public static final String CONNECTION_PROP_REQUEST_END = "REQUEST_END";
/* PROPERTIES: Client -> Proxy */
public static final String CONNECTION_PROP_CLIENT_REQUEST_HEADER = "CLIENT_REQUEST_HEADER";
/* PROPERTIES: Proxy -> Server */
/* PROPERTIES: Server -> Proxy */
/* PROPERTIES: Proxy -> Client */
public static final String CONNECTION_PROP_PROXY_RESPOND_CODE = "PROXY_RESPOND_CODE";
public static final String CONNECTION_PROP_PROXY_RESPOND_STATUS = "PROXY_RESPOND_STATUS";
public static final String CONNECTION_PROP_PROXY_RESPOND_HEADER = "PROXY_RESPOND_HEADER";
public static final String CONNECTION_PROP_PROXY_RESPOND_SIZE = "PROXY_REQUEST_SIZE";
/**
* A hashset containing extensions that indicate content that should not be transported
* using zipped content encoding
* @see #shallTransportZipped(String)
*/
private static final HashSet disallowZippedContentEncoding = new HashSet(Arrays.asList(new String[]{
".gz", ".tgz", ".jpg", ".jpeg", ".gif", ".zip", ".rar", ".bz2", ".lha", ".jar", ".rpm", ".arc", ".arj"
}));
// static objects
public static final String vDATE = "<<REPL>>";
public static final String copyright = "[ HTTP SERVER: AnomicHTTPD v" + vDATE + " by Michael Christen / www.anomic.de ]";
public static final String hline = "-------------------------------------------------------------------------------";
private static HashMap reverseMappingCache = new HashMap();
private static httpdHandler proxyHandler = null; // a servlet that holds the proxy functions
private static httpdHandler fileHandler = null; // a servlet that holds the file serving functions
private static httpdHandler soapHandler = null;
private static serverSwitch switchboard = null;
private static String virtualHost = null;
public static boolean keepAliveSupport = false;
// class objects
private serverCore.Session session; // holds the session object of the calling class
private InetAddress userAddress; // the address of the client
private boolean allowProxy;
private boolean allowServer;
// for authentication
private String proxyAccountBase64MD5;
private String serverAccountBase64MD5;
private String clientIP;
// the connection properties
private final Properties prop = new Properties();
private int emptyRequestCount = 0;
private int keepAliveRequestCount = 0;
// needed for logging
private final serverLog log = new serverLog("HTTPD");
// class methods
public httpd(serverSwitch s, httpdHandler fileHandler, httpdHandler proxyHandler) {
// handler info
httpd.switchboard = s;
httpd.fileHandler = fileHandler;
httpd.proxyHandler = proxyHandler;
httpd.virtualHost = switchboard.getConfig("fileHost","localhost");
// authentication: by default none
this.proxyAccountBase64MD5 = null;
this.serverAccountBase64MD5 = null;
this.clientIP = null;
// configuring keep alive support
keepAliveSupport = Boolean.valueOf(switchboard.getConfig("connectionKeepAliveSupport","false")).booleanValue();
}
/**
* Can be used to reset this {@link serverHandler} oject so that
* it can be reused for further connections
* @see de.anomic.server.serverHandler#reset()
*/
public void reset() {
this.session = null;
this.userAddress = null;
this.allowProxy = false;
this.allowServer = false;
this.proxyAccountBase64MD5 = null;
this.serverAccountBase64MD5 = null;
this.clientIP = null;
this.prop.clear();
this.emptyRequestCount = 0;
this.keepAliveRequestCount = 0;
}
/**
* Must be called at least once, but can be called again to re-use the object.
* @see de.anomic.server.serverHandler#initSession(de.anomic.server.serverCore.Session)
*/
public void initSession(serverCore.Session session) throws IOException {
this.session = session;
this.userAddress = session.userAddress; // client InetAddress
this.clientIP = this.userAddress.getHostAddress();
if (this.userAddress.isAnyLocalAddress()) this.clientIP = "localhost";
if (this.clientIP.equals("0:0:0:0:0:0:0:1")) this.clientIP = "localhost";
if (this.clientIP.equals("127.0.0.1")) this.clientIP = "localhost";
String proxyClient = switchboard.getConfig("proxyClient", "*");
String serverClient = switchboard.getConfig("serverClient", "*");
this.allowProxy = (proxyClient.equals("*")) ? true : match(this.clientIP, proxyClient);
this.allowServer = (serverClient.equals("*")) ? true : match(this.clientIP, serverClient);
// check if we want to allow this socket to connect us
if (!(this.allowProxy || this.allowServer)) {
String errorMsg = "CONNECTION FROM " + this.clientIP + " FORBIDDEN";
this.log.logWarning(errorMsg);
throw new IOException(errorMsg);
}
this.proxyAccountBase64MD5 = null;
this.serverAccountBase64MD5 = null;
}
private static boolean match(String key, String latch) {
// the latch is a comma-separated list of patterns
// each pattern may contain one wildcard-character '*' which matches anything
StringTokenizer st = new StringTokenizer(latch,",");
String pattern;
int pos;
while (st.hasMoreTokens()) {
pattern = st.nextToken();
if (key.matches(pattern)) return true;
/*
pos = pattern.indexOf("*");
if (pos < 0) {
// no wild card: exact match
if (key.equals(pattern)) return true;
} else {
// wild card: match left and right side of pattern
if ((key.startsWith(pattern.substring(0, pos))) &&
(key.endsWith(pattern.substring(pos + 1)))) return true;
}
*/
}
return false;
}
public String greeting() { // OBLIGATORIC FUNCTION
// a response line upon connection is send to client
// if no response line is wanted, return "" or null
return null;
}
public String error(Throwable e) { // OBLIGATORIC FUNCTION
// return string in case of any error that occurs during communication
// is always (but not only) called if an IO-dependent exception occurrs.
this.log.logError("Unexpected Error. " + e.getClass().getName(),e);
return "501 Exception occurred: " + e.getMessage();
}
/**
* reads a line from the input socket
* this function is provided by the server through a passed method on initialization
* @return the next requestline as string
*/
private String readLine() {
byte[] l = this.session.readLine();
return (l == null) ? null: new String(l);
}
private httpHeader readHeader() throws IOException {
// reading all headers
httpHeader header = new httpHeader(reverseMappingCache);
int p;
String line;
while ((line = readLine()) != null) {
if (line.length() == 0) break; // this seperates the header of the HTTP request from the body
// parse the header line: a property seperated with the ':' sign
if ((p = line.indexOf(":")) >= 0) {
// store a property
header.add(line.substring(0, p).trim(), line.substring(p + 1).trim());
}
}
/*
* doing some header validation here ...
*/
String httpVersion = this.prop.getProperty(CONNECTION_PROP_HTTP_VER, "HTTP/0.9");
if (httpVersion.equals("HTTP/1.1") && !header.containsKey(httpHeader.HOST)) {
// the HTTP/1.1 specification requires that an HTTP/1.1 server must reject any
// HTTP/1.1 message that does not contain a Host header.
httpd.sendRespondError(this.prop,this.session.out,0,400,null,null,null);
throw new IOException("400 Bad request");
}
return header;
}
private void handleTransparentProxySupport(httpHeader header) {
// transparent proxy support is only available for http 1.0 and above connections
if (this.prop.getProperty(CONNECTION_PROP_HTTP_VER, "HTTP/0.9").equals("HTTP/0.9")) return;
// if the transparent proxy support was disabled, we have nothing todo here ...
if (!(httpdProxyHandler.isTransparentProxy && header.containsKey(httpHeader.HOST))) return;
try {
String dstHost, dstHostSocket = (String) header.get(httpHeader.HOST);
int idx = dstHostSocket.indexOf(":");
dstHost = (idx != -1) ? dstHostSocket.substring(0,idx).trim() : dstHostSocket.trim();
Integer dstPort = (idx != -1) ? Integer.valueOf(dstHostSocket.substring(idx+1)) : new Integer(80);
if (dstPort.intValue() == 80) {
if (dstHost.endsWith(".yacy")) {
// if this peer is accessed via its yacy domain name we need to set the
// host property to virtualHost to redirect the request to the yacy server
if (dstHost.endsWith(yacyCore.seedDB.mySeed.getName()+".yacy")) {
this.prop.setProperty(httpd.CONNECTION_PROP_HOST,virtualHost);
} else {
this.prop.setProperty(httpd.CONNECTION_PROP_HOST,dstHostSocket);
}
} else {
InetAddress dstHostAddress = InetAddress.getByName(dstHost);
if (!(dstHostAddress.isAnyLocalAddress() || dstHostAddress.isLoopbackAddress())) {
this.prop.setProperty(httpd.CONNECTION_PROP_HOST,dstHostSocket);
}
}
}
} catch (Exception e) {}
}
/**
* This funciton is used to determine if a persistent connection was requested by the
* client.
* @param header the received http-headers
* @return <code>true</code> if a persistent connection was requested or <code>false</code> otherwise
*/
private boolean handlePersistentConnection(httpHeader header) {
if (!keepAliveSupport) {
this.prop.put(CONNECTION_PROP_PERSISTENT,"close");
return false;
}
// getting the http version that is used by the client
String httpVersion = this.prop.getProperty(CONNECTION_PROP_HTTP_VER, "HTTP/0.9");
// managing keep-alive: in HTTP/0.9 and HTTP/1.0 every connection is closed
// afterwards. In HTTP/1.1 (and above, in the future?) connections are
// persistent by default, but closed with the "Connection: close"
// property.
boolean persistent = !(httpVersion.equals("HTTP/0.9") || httpVersion.equals("HTTP/1.0"));
if (((String)header.get(httpHeader.CONNECTION, "keep-alive")).toLowerCase().equals("close") ||
((String)header.get(httpHeader.PROXY_CONNECTION, "keep-alive")).toLowerCase().equals("close")) {
persistent = false;
}
// if the request does not contain a content-length we have to close the connection
// independently of the value of the connection header
if (persistent &&
this.prop.getProperty(httpd.CONNECTION_PROP_METHOD).equals(httpHeader.METHOD_POST) &&
!header.containsKey(httpHeader.CONTENT_LENGTH))
this.prop.put(CONNECTION_PROP_PERSISTENT,"close");
else this.prop.put(CONNECTION_PROP_PERSISTENT,persistent?"keep-alive":"close");
return persistent;
}
private boolean handleServerAuthentication(httpHeader header) throws IOException {
// getting the http version that is used by the client
String httpVersion = this.prop.getProperty(CONNECTION_PROP_HTTP_VER, "HTTP/0.9");
// reading the authentication settings from switchboard
if (this.serverAccountBase64MD5 == null)
this.serverAccountBase64MD5 = switchboard.getConfig("serverAccountBase64MD5", "");
if (this.serverAccountBase64MD5.length() > 0) {
String auth = (String) header.get(httpHeader.AUTHORIZATION);
if (auth == null) {
// authorization requested, but no authorizeation given in header. Ask for authenticate:
this.session.out.write((httpVersion + " 401 log-in required" + serverCore.crlfString +
httpHeader.WWW_AUTHENTICATE + ": Basic realm=\"log-in\"" + serverCore.crlfString +
serverCore.crlfString).getBytes());
this.session.out.write((httpHeader.CONTENT_LENGTH + ": 0\r\n").getBytes());
this.session.out.write("\r\n".getBytes());
return false;
} else if (!this.serverAccountBase64MD5.equals(serverCodings.encodeMD5Hex(auth.trim().substring(6)))) {
// wrong password given: ask for authenticate again
serverLog.logInfo("HTTPD", "Wrong log-in for account 'server' in HTTPD.GET " + this.prop.getProperty("PATH") + " from IP " + this.clientIP);
this.session.out.write((httpVersion + " 401 log-in required" + serverCore.crlfString +
httpHeader.WWW_AUTHENTICATE + ": Basic realm=\"log-in\"" +
serverCore.crlfString).getBytes());
this.session.out.write((httpHeader.CONTENT_LENGTH + ": 0\r\n").getBytes());
this.session.out.write("\r\n".getBytes());
return false;
}
}
return true;
}
private boolean handleProxyAuthentication(httpHeader header) throws IOException {
// getting the http version that is used by the client
String httpVersion = this.prop.getProperty("HTTP", "HTTP/0.9");
// reading the authentication settings from switchboard
if (this.proxyAccountBase64MD5 == null)
this.proxyAccountBase64MD5 = switchboard.getConfig("proxyAccountBase64MD5", "");
if (this.proxyAccountBase64MD5.length() > 0) {
String auth = (String) header.get(httpHeader.PROXY_AUTHORIZATION,"xxxxxx");
if (!this.proxyAccountBase64MD5.equals(serverCodings.encodeMD5Hex(auth.trim().substring(6)))) {
// ask for authenticate
this.session.out.write((httpVersion + " 407 Proxy Authentication Required" + serverCore.crlfString +
httpHeader.PROXY_AUTHENTICATE + ": Basic realm=\"log-in\"" + serverCore.crlfString).getBytes());
this.session.out.write((httpHeader.CONTENT_LENGTH + ": 0\r\n").getBytes());
this.session.out.write("\r\n".getBytes());
return false;
}
}
return true;
}
public Boolean UNKNOWN(String requestLine) throws IOException {
int pos;
String unknownCommand = null, args = null;
if ((pos = requestLine.indexOf(" ")) > 0) {
unknownCommand = requestLine.substring(0,pos);
args = requestLine.substring(pos+1);
} else {
unknownCommand = requestLine;
args = "";
}
parseQuery(unknownCommand, args);
String httpVersion = this.prop.getProperty(httpd.CONNECTION_PROP_HTTP_VER,"HTTP/0.9");
sendRespondError(this.prop,this.session.out,0,501,null,unknownCommand + " method not implemented",null);
return serverCore.TERMINATE_CONNECTION;
}
public Boolean EMPTY(String arg) throws IOException {
if (++this.emptyRequestCount > 10) return serverCore.TERMINATE_CONNECTION;
return serverCore.RESUME_CONNECTION;
}
public Boolean TRACE(String arg) throws IOException {
sendRespondError(this.prop,this.session.out,0,501,null,"TRACE method not implemented",null);
return serverCore.TERMINATE_CONNECTION;
}
public Boolean OPTIONS(String arg) throws IOException {
sendRespondError(this.prop,this.session.out,0,501,null,"OPTIONS method not implemented",null);
return serverCore.TERMINATE_CONNECTION;
}
public Boolean GET(String arg) throws IOException {
try {
// parsing the http request line
parseQuery(httpHeader.METHOD_GET,arg);
// we now know the HTTP version. depending on that, we read the header
String httpVersion = this.prop.getProperty(CONNECTION_PROP_HTTP_VER, "HTTP/0.9");
httpHeader header = (httpVersion.equals("HTTP/0.9")) ? new httpHeader(reverseMappingCache)
: readHeader();
// handling transparent proxy support
this.handleTransparentProxySupport(header);
// determines if the connection should be kept alive
handlePersistentConnection(header);
if (this.prop.getProperty(CONNECTION_PROP_HOST).equals(virtualHost)) {
// pass to server
if (this.allowServer) {
/*
* Handling SOAP Requests here ...
*/
if (this.prop.containsKey(CONNECTION_PROP_PATH) && this.prop.getProperty(CONNECTION_PROP_PATH).startsWith("/soap")) {
if (soapHandler == null) {
try {
Class soapHandlerClass = Class.forName("de.anomic.soap.httpdSoapHandler");
Constructor classConstructor = soapHandlerClass.getConstructor( new Class[] { serverSwitch.class } );
soapHandler = (httpdHandler) classConstructor.newInstance(new Object[] { this.switchboard });
} catch (Exception e) {
sendRespondHeader(this.prop,this.session.out,httpVersion,503,null);
return serverCore.TERMINATE_CONNECTION;
} catch (NoClassDefFoundError e) {
sendRespondError(this.prop,this.session.out,4,503,null,"SOAP Extension not installed",null);
return serverCore.TERMINATE_CONNECTION;
} catch (Error e) {
sendRespondHeader(this.prop,this.session.out,httpVersion,503,null);
return serverCore.TERMINATE_CONNECTION;
}
}
soapHandler.doGet(this.prop, header, this.session.out);
/*
* Handling HTTP requests here ...
*/
} else {
if (this.handleServerAuthentication(header)) {
if (fileHandler == null) fileHandler = new httpdFileHandler(this.switchboard);
fileHandler.doGet(this.prop, header, this.session.out);
}
}
} else {
// not authorized through firewall blocking (ip does not match filter)
this.session.out.write((httpVersion + " 403 refused (IP not granted)" + serverCore.crlfString + serverCore.crlfString + "you are not allowed to connect to this server, because you are using the non-granted IP " + clientIP + ". allowed are only connections that match with the following filter: " + switchboard.getConfig("serverClient", "*") + serverCore.crlfString).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
} else {
// pass to proxy
if (this.allowProxy) {
if (this.handleProxyAuthentication(header)) {
if (proxyHandler != null) proxyHandler = new httpdProxyHandler(this.switchboard);
proxyHandler.doGet(this.prop, header, this.session.out);
}
} else {
// not authorized through firewall blocking (ip does not match filter)
this.session.out.write((httpVersion + " 403 refused (IP not granted)" + serverCore.crlfString + serverCore.crlfString + "you are not allowed to connect to this proxy, because you are using the non-granted IP " + clientIP + ". allowed are only connections that match with the following filter: " + switchboard.getConfig("proxyClient", "*") + serverCore.crlfString).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
}
return this.prop.getProperty(CONNECTION_PROP_PERSISTENT).equals("keep-alive") ? serverCore.RESUME_CONNECTION : serverCore.TERMINATE_CONNECTION;
} catch (Exception e) {
logUnexpectedError(e);
return serverCore.TERMINATE_CONNECTION;
}
}
private void logUnexpectedError(Exception e) {
if (e instanceof InterruptedException) {
this.log.logInfo("Interruption detected");
} else {
String errorMsg = e.getMessage();
if (errorMsg != null) {
if (errorMsg.startsWith("Socket closed")) {
this.log.logInfo("httpd shutdown detected ...");
} else if ((errorMsg.startsWith("Broken pipe") || errorMsg.startsWith("Connection reset"))) {
// client closed the connection, so we just end silently
this.log.logInfo("Client unexpectedly closed connection");
} else {
this.log.logError("Unexpected Error. " + e.getClass().getName() + ": " + e.getMessage(),e);
}
} else {
this.log.logError("Unexpected Error. " + e.getClass().getName(),e);
}
}
}
public Boolean HEAD(String arg) throws IOException {
try {
parseQuery(httpHeader.METHOD_HEAD,arg);
// we now know the HTTP version. depending on that, we read the header
httpHeader header;
String httpVersion = prop.getProperty("HTTP", "HTTP/0.9");
if (httpVersion.equals("HTTP/0.9")) header = new httpHeader(reverseMappingCache);
else header = readHeader();
// handle transparent proxy support
this.handleTransparentProxySupport(header);
// determines if the connection should be kept alive
boolean persistent = handlePersistentConnection(header);
// return multi-line message
if (this.prop.getProperty("HOST").equals(virtualHost)) {
// pass to server
if (allowServer) {
if (handleServerAuthentication(header)) {
if (fileHandler == null) fileHandler = new httpdFileHandler(this.switchboard);
fileHandler.doHead(prop, header, this.session.out);
}
} else {
// not authorized through firewall blocking (ip does not match filter)
session.out.write((httpVersion + " 403 refused (IP not granted)" +
serverCore.crlfString).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
} else {
// pass to proxy
if (allowProxy) {
if (handleProxyAuthentication(header)) {
if (proxyHandler != null) proxyHandler = new httpdProxyHandler(this.switchboard);
proxyHandler.doHead(prop, header, this.session.out);
}
} else {
// not authorized through firewall blocking (ip does not match filter)
session.out.write((httpVersion + " 403 refused (IP not granted)" +
serverCore.crlfString).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
}
return this.prop.getProperty(CONNECTION_PROP_PERSISTENT).equals("keep-alive") ? serverCore.RESUME_CONNECTION : serverCore.TERMINATE_CONNECTION;
} catch (Exception e) {
logUnexpectedError(e);
return serverCore.TERMINATE_CONNECTION;
}
}
public Boolean POST(String arg) throws IOException {
try {
parseQuery("POST",arg);
// we now know the HTTP version. depending on that, we read the header
httpHeader header;
String httpVersion = prop.getProperty("HTTP", "HTTP/0.9");
if (httpVersion.equals("HTTP/0.9")) header = new httpHeader(reverseMappingCache);
else header = readHeader();
// handle transparent proxy support
this.handleTransparentProxySupport(header);
// determines if the connection should be kept alive
boolean persistent = handlePersistentConnection(header);
// return multi-line message
if (prop.getProperty("HOST").equals(virtualHost)) {
// pass to server
if (allowServer) {
/*
* Handling SOAP Requests here ...
*/
if (this.prop.containsKey("PATH") && this.prop.getProperty("PATH").startsWith("/soap")) {
if (soapHandler == null) {
try {
// creating the soap handler class by name
Class soapHandlerClass = Class.forName("de.anomic.soap.httpdSoapHandler");
// Look for the proper constructor
Constructor soapHandlerConstructor = soapHandlerClass.getConstructor( new Class[] { serverSwitch.class } );
// creating the new object
soapHandler = (httpdHandler)soapHandlerConstructor.newInstance( new Object[] { this.switchboard } );
} catch (Exception e) {
sendRespondHeader(this.prop,this.session.out,httpVersion,503,null);
return serverCore.TERMINATE_CONNECTION;
} catch (NoClassDefFoundError e) {
sendRespondError(this.prop,this.session.out,4,503,"SOAP Extension not installed","SOAP Extension not installed",null);
return serverCore.TERMINATE_CONNECTION;
} catch (Error e) {
sendRespondHeader(this.prop,this.session.out,httpVersion,503,null);
return serverCore.TERMINATE_CONNECTION;
}
}
soapHandler.doPost(prop, header, this.session.out, this.session.in);
/*
* Handling normal HTTP requests here ...
*/
} else {
if (handleServerAuthentication(header)) {
if (fileHandler == null) fileHandler = new httpdFileHandler(this.switchboard);
fileHandler.doPost(prop, header, this.session.out, this.session.in);
}
}
} else {
// not authorized through firewall blocking (ip does not match filter)
session.out.write((httpVersion + " 403 refused (IP not granted)" + serverCore.crlfString + serverCore.crlfString + "you are not allowed to connect to this server, because you are using the non-granted IP " + clientIP + ". allowed are only connections that match with the following filter: " + switchboard.getConfig("serverClient", "*") + serverCore.crlfString).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
} else {
// pass to proxy
if (allowProxy) {
if (handleProxyAuthentication(header)) {
if (proxyHandler != null) proxyHandler = new httpdProxyHandler(this.switchboard);
proxyHandler.doPost(prop, header, this.session.out, this.session.in);
}
} else {
// not authorized through firewall blocking (ip does not match filter)
session.out.write((httpVersion + " 403 refused (IP not granted)" + serverCore.crlfString + serverCore.crlfString + "you are not allowed to connect to this proxy, because you are using the non-granted IP " + clientIP + ". allowed are only connections that match with the following filter: " + switchboard.getConfig("proxyClient", "*") + serverCore.crlfString).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
}
//return serverCore.RESUME_CONNECTION;
return this.prop.getProperty(CONNECTION_PROP_PERSISTENT).equals("keep-alive") ? serverCore.RESUME_CONNECTION : serverCore.TERMINATE_CONNECTION;
} catch (Exception e) {
logUnexpectedError(e);
return serverCore.TERMINATE_CONNECTION;
}
}
public Boolean CONNECT(String arg) throws IOException {
// establish a ssh-tunneled http connection
// this is to support https
// parse HTTP version
int pos = arg.indexOf(" ");
String httpVersion = "HTTP/1.0";
if (pos >= 0) {
httpVersion = arg.substring(pos + 1);
arg = arg.substring(0, pos);
}
if (!(allowProxy)) {
// not authorized through firewall blocking (ip does not match filter)
session.out.write((httpVersion + " 403 refused (IP not granted)" + serverCore.crlfString + serverCore.crlfString + "you are not allowed to connect to this proxy, because you are using the non-granted IP " + clientIP + ". allowed are only connections that match with the following filter: " + switchboard.getConfig("proxyClient", "*") + serverCore.crlfString).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
// parse port
pos = arg.indexOf(":");
int port = 443;
if (pos >= 0) {
port = Integer.parseInt(arg.substring(pos + 1));
arg = arg.substring(0, pos);
}
// arg is now the host string
// parse remaining lines
httpHeader header = readHeader();
if (port != 443) {
// security: connection only to ssl port
// we send a 403 (forbidden) error back
session.out.write((httpVersion + " 403 Connection to non-443 forbidden" +
serverCore.crlfString + serverCore.crlfString).getBytes());
return serverCore.TERMINATE_CONNECTION;
}
// prepare to pass values
Properties prop = new Properties();
prop.setProperty("HOST", arg);
prop.setProperty("PORT", Integer.toString(port));
prop.setProperty("HTTP", httpVersion);
// pass to proxy
if (allowProxy) {
if (handleProxyAuthentication(header)) {
if (proxyHandler != null) proxyHandler = new httpdProxyHandler(this.switchboard);
proxyHandler.doConnect(prop, header, this.session.in, this.session.out);
}
} else {
// not authorized through firewall blocking (ip does not match filter)
session.out.write((httpVersion + " 403 refused (IP not granted)" + serverCore.crlfString + serverCore.crlfString + "you are not allowed to connect to this proxy, because you are using the non-granted IP " + clientIP + ". allowed are only connections that match with the following filter: " + switchboard.getConfig("proxyClient", "*") + serverCore.crlfString).getBytes());
}
return serverCore.TERMINATE_CONNECTION;
}
private final Properties parseQuery(String cmd, String s) {
// reset property from previous run
this.prop.clear();
this.emptyRequestCount = 0;
// storing informations about the request
this.prop.setProperty(CONNECTION_PROP_METHOD, cmd);
this.prop.setProperty(CONNECTION_PROP_REQUESTLINE,cmd + " " + s);
this.prop.setProperty(CONNECTION_PROP_CLIENTIP, this.clientIP);
// counting the amount of received requests within this permanent conneciton
this.prop.setProperty(CONNECTION_PROP_KEEP_ALIVE_COUNT, Integer.toString(++this.keepAliveRequestCount));
// this parses a whole URL
if (s.length() == 0) {
this.prop.setProperty(CONNECTION_PROP_HOST, virtualHost);
this.prop.setProperty(CONNECTION_PROP_PATH, "/");
this.prop.setProperty(CONNECTION_PROP_HTTP_VER, "HTTP/0.9");
this.prop.setProperty(CONNECTION_PROP_EXT, "");
return this.prop;
}
// store the version propery "HTTP" and cut the query at both ends
int sep = s.indexOf(" ");
if (sep >= 0) {
// HTTP version is given
this.prop.setProperty(CONNECTION_PROP_HTTP_VER, s.substring(sep + 1).trim());
s = s.substring(0, sep).trim(); // cut off HTTP version mark
} else {
// HTTP version is not given, it will be treated as ver 0.9
this.prop.setProperty(CONNECTION_PROP_HTTP_VER, "HTTP/0.9");
}
// properties of the query are stored with the prefix "&"
// additionally, the values URL and ARGC are computed
String argsString = "";
sep = s.indexOf("?");
if (sep >= 0) {
// there are values attached to the query string
argsString = s.substring(sep + 1); // cut haed from tail of query
s = s.substring(0, sep);
}
this.prop.setProperty(CONNECTION_PROP_URL, s); // store URL
//System.out.println("HTTPD: ARGS=" + argsString);
if (argsString.length() != 0) this.prop.setProperty(CONNECTION_PROP_ARGS, argsString); // store arguments in original form
// find out file extension
sep = s.lastIndexOf(".");
if (sep >= 0) {
if (s.indexOf("?", sep + 1) >= sep)
this.prop.setProperty(CONNECTION_PROP_EXT, s.substring(sep + 1, s.indexOf("?", sep + 1)).toLowerCase());
else if (s.indexOf("#", sep + 1) >= sep)
this.prop.setProperty(CONNECTION_PROP_EXT, s.substring(sep + 1, s.indexOf("#", sep + 1)).toLowerCase());
else
this.prop.setProperty(CONNECTION_PROP_EXT, s.substring(sep + 1).toLowerCase());
} else {
this.prop.setProperty(CONNECTION_PROP_EXT, "");
}
// finally find host string
if (s.toUpperCase().startsWith("HTTP://")) {
// a host was given. extract it and set path
s = s.substring(7);
sep = s.indexOf("/");
if (sep < 0) {
// this is a malformed url, something like
// http://index.html
// we are lazy and guess that it means
// /index.html
// which is a localhost access to the file servlet
this.prop.setProperty(CONNECTION_PROP_HOST, virtualHost);
this.prop.setProperty(CONNECTION_PROP_PATH, "/" + s);
} else {
// THIS IS THE "GOOD" CASE
// a perfect formulated url
this.prop.setProperty(CONNECTION_PROP_HOST, s.substring(0, sep));
this.prop.setProperty(CONNECTION_PROP_PATH, s.substring(sep)); // yes, including beginning "/"
}
} else {
// no host in url. set path
if (s.startsWith("/")) {
// thats also fine, its a perfect localhost access
// in this case, we simulate a
// http://localhost/s
// access by setting a virtual host
this.prop.setProperty(CONNECTION_PROP_HOST, virtualHost);
this.prop.setProperty(CONNECTION_PROP_PATH, s);
} else {
// the client 'forgot' to set a leading '/'
// this is the same case as above, with some lazyness
this.prop.setProperty(CONNECTION_PROP_HOST, virtualHost);
this.prop.setProperty(CONNECTION_PROP_PATH, "/" + s);
}
}
return this.prop;
}
// some static methods that needs to be used from any CGI
// and also by the httpdFileHandler
// but this belongs to the protocol handler, this class.
public static int parseArgs(serverObjects args, PushbackInputStream in, int length) throws IOException {
// this is a quick hack using a previously coded parseMultipart based on a buffer
// should be replaced sometime by a 'right' implementation
byte[] buffer = new byte[length];
in.read(buffer);
int argc = parseArgs(args, new String(buffer));
buffer = null;
return argc;
}
public static int parseArgs(serverObjects args, String argsString) {
// this parses a arg string that can either be attached to a URL query
// or can be given as result of a post method
// the String argsString is supposed to be constructed as
// <key1>=<value1>'&'<key2>=<value2>'&'<key3>=<value3>
// the calling function must strip off a possible leading '?' char
if (argsString.length() == 0) return 0;
argsString = argsString + "&"; // for technical reasons
int sep;
int eqp;
int argc = 0;
// Textfield1=default+value+Textfield+1&Textfield2=default+value+Textfield+2&selection1=sel1&selection2=othervalue1&selection2=sel2&selection3=sel3&Menu1=SubEnry11&radio1=button1&check1=button2&check1=button3&hidden1=&sButton1=enter+%281%29
while (argsString.length() > 0) {
eqp = argsString.indexOf("=");
sep = argsString.indexOf("&");
if ((eqp <= 0) || (sep <= 0)) break;
// resulting equations are inserted into the property args with leading '&'
args.put(parseArg(argsString.substring(0, eqp)), parseArg(argsString.substring(eqp + 1, sep)));
argsString = argsString.substring(sep + 1);
argc++;
}
// we return the number of parsed arguments
return argc;
}
private static String parseArg(String s) {
// this parses a given value-string from a http property
// we replace all "+" by spaces
// and resolve %-escapes with two-digit hex attributes
int pos = 0;
StringBuffer result = new StringBuffer(s.length());
while (pos < s.length()) {
if (s.charAt(pos) == '+') {
result.append(" ");
pos++;
} else if (s.charAt(pos) == '%') {
result.append((char) Integer.parseInt(s.substring(pos + 1, pos + 3), 16));
pos += 3;
} else {
result.append(s.charAt(pos++));
}
}
return result.toString();
}
public static HashMap parseMultipart(httpHeader header, serverObjects args, PushbackInputStream in, int length) throws IOException {
// this is a quick hack using a previously coded parseMultipart based on a buffer
// should be replaced sometime by a 'right' implementation
byte[] buffer = new byte[length];
int c, a = 0;
while (a < length) {
c = in.read(buffer, a, length - a);
if (c <= 0) break;
a += c;
}
//System.out.println("MULTIPART-BUFFER=" + new String(buffer));
HashMap files = parseMultipart(header, args, buffer);
buffer = null;
return files;
}
public static HashMap parseMultipart(httpHeader header, serverObjects args, byte[] buffer) throws IOException {
// we parse a multipart message and put results into the properties
// find/identify boundary marker
//System.out.println("DEBUG parseMultipart = <<" + new String(buffer) + ">>");
String s = (String) header.get(httpHeader.CONTENT_TYPE);
if (s == null) return null;
int q;
int p = s.toLowerCase().indexOf("boundary=");
if (p < 0) throw new IOException("boundary marker in multipart not found");
// boundaries start with additional leading "--", see RFC1867
byte[] boundary = ("--" + s.substring(p + 9)).getBytes();
// eat up first boundary
// the buffer must start with a boundary
byte[] line = readLine(0, buffer);
int pos = nextPos;
if ((line == null) || (!(equals(line, 0, boundary, 0, boundary.length))))
throw new IOException("boundary not recognized: " + ((line == null) ? "NULL" : new String(line)) + ", boundary = " + new String(boundary));
// we need some constants
byte[] namec = (new String("name=")).getBytes();
byte[] filenamec = (new String("filename=")).getBytes();
byte[] semicolonc = (new String(";")).getBytes();
byte[] quotec = new byte[] {(byte) '"'};
// now loop over boundaries
byte [] name;
byte [] filename;
HashMap files = new HashMap();
int argc = 0;
//System.out.println("DEBUG: parsing multipart body:" + new String(buffer));
while (pos < buffer.length) { // boundary enumerator
// here the 'pos' marker points to the first line in a section after a boundary line
line = readLine(pos, buffer); pos = nextPos;
// termination if line is empty
if (line.length == 0) break;
// find name tag in line
p = indexOf(0, line, namec);
if (p < 0) throw new IOException("tag name in marker section not found: '" + new String(line) + "'"); // a name tag must always occur
p += namec.length + 1; // first position of name value
q = indexOf(p, line, quotec);
if (q < 0) throw new IOException("missing quote in name tag: '" + new String(line) + "'");
name = new byte[q - p];
java.lang.System.arraycopy(line, p, name, 0, q - p);
// if this line has also a filename attribute, read it
p = indexOf(q, line, filenamec);
if (p > 0) {
p += filenamec.length + 1; // first position of name value
q = indexOf(p, line, quotec);
if (q < 0) throw new IOException("missing quote in filename tag: '" + new String(line) + "'");
filename = new byte[q - p];
java.lang.System.arraycopy(line, p, filename, 0, q - p);
} else filename = null;
// we have what we need. more information lines may follow, but we omit parsing them
// we just skip until an empty line is reached
while (pos < buffer.length) { // line skiping
line = readLine(pos, buffer); pos = nextPos;
if ((line == null) || (line.length == 0)) break;
}
// depending on the filename tag exsistence, read now either a value for the name
// or a complete uploaded file
// to know the exact length of the value, we must identify the next boundary
p = indexOf(pos, buffer, boundary);
// if we can't find another boundary, then this is an error in the input
if (p < 0) {
serverLog.logError("HTTPD", "ERROR in PUT body: no ending boundary. probably missing values");
break;
}
// we don't know if the value is terminated by lf, cr or crlf
// (it's suppose to be crlf, but we want to be lazy about wrong terminations)
if (buffer[p - 2] == serverCore.cr) // ERROR: IndexOutOfBounds: -2
/* crlf */ q = p - 2;
else
/* cr or lf only */ q = p - 1;
// the above line is wrong if we uploaded a file that has a cr as it's last byte
// and the client's line termination symbol is only a cr or lf (which would be incorrect)
// the value is between 'pos' and 'q', while the next marker is 'p'
line = new byte[q - pos];
java.lang.System.arraycopy(buffer, pos, line, 0, q - pos);
// in the 'line' variable we have now either a normal value or an uploadef file
if (filename == null) {
args.put(new String(name), new String(line, "ISO-8859-1"));
} else {
// we store the file in a hashtable.
// we use the same key to address the file in the hashtable as we
// use to address the filename in the properties, but without leading '&'
args.put(new String(name), new String(filename));
files.put(new String(name), line);
}
argc++;
// finally, read the next boundary line
line = readLine(p, buffer);
pos = nextPos;
}
header.put("ARGC", Integer.toString(argc)); // store argument count
return files;
}
/*
------------1090358578442
Content-Disposition: form-data; name="youare"
Ty2F86ekSWM5
------------1090358578442
Content-Disposition: form-data; name="key"
6EkPPOl7
------------1090358578442
Content-Disposition: form-data; name="iam"
HnTvzwV7SCJR
------------1090358578442
Content-Disposition: form-data; name="process"
permission
------------1090358578442
*/
static int nextPos = -1;
private static byte[] readLine(int start, byte[] array) {
// read a string from an array; line ending is always CRLF
// but we are also fuzzy with that: may also be only CR or LF
// if no remaining cr, crlf or lf can be found, return null
if (start > array.length) return null;
int pos = indexOf(start, array, serverCore.crlf); nextPos = pos + 2;
if (pos < 0) {pos = indexOf(start, array, new byte[] {serverCore.cr}); nextPos = pos + 1;}
if (pos < 0) {pos = indexOf(start, array, new byte[] {serverCore.lf}); nextPos = pos + 1;}
if (pos < 0) {nextPos = start; return null;}
byte[] result = new byte[pos - start];
java.lang.System.arraycopy(array, start, result, 0, pos - start);
return result;
}
public static int indexOf(int start, byte[] array, byte[] pattern) {
// return a position of a pattern in an array
if (start > array.length - pattern.length) return -1;
if (pattern.length == 0) return start;
int i;
for (int pos = start; pos <= array.length - pattern.length; pos++)
if ((array[pos] == pattern[0]) && (equals(array, pos, pattern, 0, pattern.length)))
return pos;
return -1;
}
public static boolean equals(byte[] a, int aoff, byte[] b, int boff, int len) {
//System.out.println("equals: a = " + new String(a) + ", aoff = " + aoff + ", b = " + new String(b) + ", boff = " + boff + ", length = " + len);
if ((aoff + len > a.length) || (boff + len > b.length)) return false;
for (int i = 0; i < len; i++) if (a[aoff + i] != b[boff + i]) return false;
//System.out.println("TRUE!");
return true;
}
public Object clone() {
return new httpd(this.switchboard, this.fileHandler, this.proxyHandler);
}
public static final void sendRespondBody(
Properties conProp,
OutputStream respond,
byte[] body
) throws IOException {
respond.write(body);
respond.flush();
}
public static final void sendRespondError(
Properties conProp,
OutputStream respond,
int errorcase,
int httpStatusCode,
String httpStatusText,
String detailedErrorMsg,
Exception stackTrace
) throws IOException {
FileInputStream fis = null;
ByteArrayOutputStream o = null;
try {
// setting the proper http status message
String httpVersion = conProp.getProperty(httpd.CONNECTION_PROP_HTTP_VER,"HTTP/1.1");
if ((httpStatusText == null)||(httpStatusText.length()==0)) {
if (httpVersion.equals("HTTP/1.0") && httpHeader.http1_0.containsKey(Integer.toString(httpStatusCode)))
httpStatusText = (String) httpHeader.http1_0.get(Integer.toString(httpStatusCode));
else if (httpVersion.equals("HTTP/1.1") && httpHeader.http1_1.containsKey(Integer.toString(httpStatusCode)))
httpStatusText = (String) httpHeader.http1_1.get(Integer.toString(httpStatusCode));
else httpStatusText = "Unknown";
}
// generating the desired request url
String host = conProp.getProperty(httpd.CONNECTION_PROP_HOST);
String path = conProp.getProperty(httpd.CONNECTION_PROP_PATH);
String args = conProp.getProperty(httpd.CONNECTION_PROP_ARGS);
int port = 80, pos = host.indexOf(":");
if (pos != -1) {
port = Integer.parseInt(host.substring(pos + 1));
host = host.substring(0, pos);
}
String urlString;
try {
urlString = (new URL("http", host, port, (args == null) ? path : path + "?" + args)).toString();
} catch (MalformedURLException e) {
urlString = "invalid URL";
}
// set rewrite values
serverObjects tp = new serverObjects();
// tp.put("host", serverCore.publicIP().getHostAddress());
// tp.put("port", switchboard.getConfig("port", "8080"));
tp.put("peerName", yacyCore.seedDB.mySeed.getName());
tp.put("host", serverCore.publicIP());
tp.put("port", (serverCore.portForwardingEnabled && (serverCore.portForwarding != null))
? Integer.toString(serverCore.portForwarding.getPort())
: switchboard.getConfig("port", "8080"));
tp.put("errorMessageType", errorcase);
tp.put("httpStatus", Integer.toString(httpStatusCode) + " " + httpStatusText);
tp.put("requestMethod", conProp.getProperty(httpd.CONNECTION_PROP_METHOD));
tp.put("requestURL", urlString);
tp.put("errorMessageType_detailedErrorMsg",(detailedErrorMsg != null) ? detailedErrorMsg : "");
// building the stacktrace
if (stackTrace != null) {
tp.put("printStackTrace",1);
serverByteBuffer errorMsg = new serverByteBuffer(100);
errorMsg.append("<i>Exception occurred:</i>&nbsp;<b>")
.append(stackTrace.toString())
.append("</b>\r\n\r\n")
.append("</i>TRACE:</i>\r\n");
stackTrace.printStackTrace(new PrintStream(errorMsg));
errorMsg.append("\r\n");
tp.put("printStackTrace_stacktrace",errorMsg.toString().replaceAll("\n","<br>"));
} else {
tp.put("printStackTrace",0);
}
// Generated Tue, 23 Aug 2005 11:19:14 GMT by brain.wg (squid/2.5.STABLE3)
// adding some system information
String systemDate = httpc.dateString(httpc.nowDate());
tp.put("date",systemDate);
// rewrite the file
File htRootPath = new File(switchboard.getRootPath(), switchboard.getConfig("htRootPath","htroot"));
httpTemplate.writeTemplate(
fis = new FileInputStream(new File(htRootPath, "/proxymsg/error.html")),
o = new ByteArrayOutputStream(),
tp,
"-UNRESOLVED_PATTERN-".getBytes()
);
byte[] result = o.toByteArray();
o.close(); o = null;
httpHeader header = new httpHeader();
header.put(httpHeader.DATE, systemDate);
header.put(httpHeader.CONTENT_TYPE, "text/html");
header.put(httpHeader.CONTENT_LENGTH, Integer.toString(result.length));
header.put(httpHeader.PRAGMA, "no-cache");
sendRespondHeader(conProp,respond,httpVersion,httpStatusCode,httpStatusText,header);
// write the array to the client
serverFileUtils.write(result, respond);
respond.flush();
} catch (Exception e) {
throw new IOException(e.getMessage());
} finally {
if (fis != null) try { fis.close(); } catch (Exception e) {}
if (o != null) try { o.close(); } catch (Exception e) {}
}
}
public static final void sendRespondHeader(
Properties conProp,
OutputStream respond,
String httpVersion,
int httpStatusCode,
String httpStatusText,
long contentLength
) throws IOException {
sendRespondHeader(conProp,respond,httpVersion,httpStatusCode,httpStatusText,null,contentLength,null,null,null,null,null);
}
public static final void sendRespondHeader(
Properties conProp,
OutputStream respond,
String httpVersion,
int httpStatusCode,
String httpStatusText,
String contentType,
long contentLength,
Date moddate,
Date expires,
String cookie,
String contentEnc,
String transferEnc
) throws IOException {
httpHeader headers = new httpHeader();
headers.put(httpHeader.SERVER, "AnomicHTTPD (www.anomic.de)");
headers.put(httpHeader.DATE, httpc.dateString(httpc.nowDate()));
headers.put(httpHeader.LAST_MODIFIED, httpc.dateString(moddate));
headers.put(httpHeader.PRAGMA, "no-cache");
if (contentLength > 0) headers.put(httpHeader.CONTENT_TYPE, (contentType == null)? "text/html" : contentType);
if (contentLength > 0) headers.put(httpHeader.CONTENT_LENGTH, Long.toString(contentLength));
if (cookie != null) headers.put(httpHeader.SET_COOKIE, cookie);
if (expires != null) headers.put(httpHeader.EXPIRES, httpc.dateString(expires));
if (contentEnc != null) headers.put(httpHeader.CONTENT_ENCODING, contentEnc);
if (transferEnc != null) headers.put(httpHeader.TRANSFER_ENCODING, transferEnc);
sendRespondHeader(conProp, respond, httpVersion, httpStatusCode, httpStatusText, headers);
}
public static final void sendRespondHeader(
Properties conProp,
OutputStream respond,
String httpVersion,
int httpStatusCode,
httpHeader header
) throws IOException {
sendRespondHeader(conProp,respond,httpVersion,httpStatusCode,null,header);
}
public static final void sendRespondHeader(
Properties conProp,
OutputStream respond,
String httpVersion,
int httpStatusCode,
String httpStatusText,
httpHeader header
) throws IOException {
if (respond == null) throw new NullPointerException("The outputstream must not be null.");
if (conProp == null) throw new NullPointerException("The connection property structure must not be null.");
if (httpVersion == null) httpVersion = conProp.getProperty(httpd.CONNECTION_PROP_HTTP_VER,"HTTP/1.1");
try {
if ((httpStatusText == null)||(httpStatusText.length()==0)) {
if (httpVersion.equals("HTTP/1.0") && httpHeader.http1_0.containsKey(Integer.toString(httpStatusCode)))
httpStatusText = (String) httpHeader.http1_0.get(Integer.toString(httpStatusCode));
else if (httpVersion.equals("HTTP/1.1") && httpHeader.http1_1.containsKey(Integer.toString(httpStatusCode)))
httpStatusText = (String) httpHeader.http1_1.get(Integer.toString(httpStatusCode));
else httpStatusText = "Unknown";
}
// prepare header
if (!header.containsKey(httpHeader.DATE))
header.put(httpHeader.DATE, httpc.dateString(httpc.nowDate()));
if (!header.containsKey(httpHeader.CONTENT_TYPE))
header.put(httpHeader.CONTENT_TYPE, "text/html"); // fix this
if (!header.containsKey(httpHeader.CONNECTION) && conProp.containsKey(CONNECTION_PROP_PERSISTENT))
header.put(httpHeader.CONNECTION, conProp.getProperty(CONNECTION_PROP_PERSISTENT));
if (!header.containsKey(httpHeader.PROXY_CONNECTION) && conProp.containsKey(CONNECTION_PROP_PERSISTENT))
header.put(httpHeader.PROXY_CONNECTION, conProp.getProperty(CONNECTION_PROP_PERSISTENT));
if (conProp.containsKey(CONNECTION_PROP_PERSISTENT) &&
conProp.getProperty(CONNECTION_PROP_PERSISTENT).equals("keep-alive") &&
!header.containsKey(httpHeader.TRANSFER_ENCODING) &&
!header.containsKey(httpHeader.CONTENT_LENGTH))
header.put(httpHeader.CONTENT_LENGTH, "0");
// adding some yacy specific headers
header.put(httpHeader.X_YACY_KEEP_ALIVE_REQUEST_COUNT,conProp.getProperty(CONNECTION_PROP_KEEP_ALIVE_COUNT));
header.put(httpHeader.X_YACY_ORIGINAL_REQUEST_LINE,conProp.getProperty(CONNECTION_PROP_REQUESTLINE));
StringBuffer headerStringBuffer = new StringBuffer(560);
// write status line
headerStringBuffer.append(httpVersion).append(" ")
.append(Integer.toString(httpStatusCode)).append(" ")
.append(httpStatusText).append("\r\n");
// write header
Iterator i = header.keySet().iterator();
String key, value;
char tag;
int count;
//System.out.println("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv");
while (i.hasNext()) {
key = (String) i.next();
tag = key.charAt(0);
if ((tag != '*') && (tag != '#')) { // '#' in key is reserved for proxy attributes as artificial header values
count = header.keyCount(key);
for (int j = 0; j < count; j++) {
headerStringBuffer.append(key).append(": ").append((String) header.getSingle(key, j)).append("\r\n");
}
//System.out.println("#" + key + ": " + value);
}
}
// end header
headerStringBuffer.append("\r\n");
// sending headers to the client
respond.write(headerStringBuffer.toString().getBytes());
respond.flush();
conProp.put(httpd.CONNECTION_PROP_PROXY_RESPOND_HEADER,header);
conProp.put(httpd.CONNECTION_PROP_PROXY_RESPOND_STATUS,Integer.toString(httpStatusCode));
} catch (Exception e) {
// any interruption may be caused be network error or because the user has closed
// the windows during transmission. We simply pass it as IOException
throw new IOException(e.getMessage());
}
}
public static boolean shallTransportZipped(String path) {
if ((path == null) || (path.length() == 0)) return true;
int pos;
if ((pos = path.lastIndexOf(".")) != -1) {
return !disallowZippedContentEncoding.contains(path.substring(pos).toLowerCase());
}
return true;
}
// public static boolean isTextMime(String mime, Set whitelist) {
// if (whitelist.contains(mime)) return true;
// // some mime-types are given as "text/html; charset=...", so look for ";"
// if (mime.length() == 0) return false;
// int pos = mime.indexOf(';');
// if (pos < 0) return false;
// return whitelist.contains(mime.substring(0, pos));
// }
}
/*
###
### Messages of the Server
###
# success Messages
HTTPStatus200 = OK; The URL was found. It contents follows.
HTTPStatus201 = Created; A URL was created in response to a POST.
HTTPStatus202 = Accepted; The request was accepted for processing later.
HTTPStatus203 = Non-Authoritative; The information here is unofficial.
HTTPStatus204 = No Response; The request is successful, but there is no data to send.
# redirection
HTTPStatus300 = Moved; The URL has permanently moved to a new location.
HTTPStatus301 = Found; The URL can be temporarily found at a new location.
# client errors
HTTPStatus400 = Bad Request; Syntax error in the request.
HTTPStatus401 = Unauthorized; The client is not authorized to access this web page.
HTTPStatus402 = Payment Required; A payment is required to access this web page.
HTTPStatus403 = Forbidden; This URL is forbidden. No authorization is required, it won't help.
HTTPStatus404 = Not Found; This page is not on the server.
# server errors
HTTPStatus500 = Internal Error; The server encountered an unexpected error.
HTTPStatus501 = Not Implemented; The client requested an unimplemented feature.
HTTPStatus502 = Service Overloaded; The server reached the maximum number of connections.
HTTPStatus503 = Gateway timeout; Fetching data from remote service failed.
*/