You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
yacy_search_server/source/de/anomic/icap/icapHeader.java

287 lines
12 KiB

//icapHeader.java
//-----------------------
//(C) by Michael Peter Christen; mc@yacy.net
//first published on http://www.anomic.de
//Frankfurt, Germany, 2004
//
//This file is contributed by Martin Thelian
//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
package de.anomic.icap;
import java.text.Collator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import de.anomic.server.serverCore;
public class icapHeader extends TreeMap<String, String> implements Map<String, String> {
private static final long serialVersionUID = 1L;
/* =============================================================
* Constants defining icap methods
* ============================================================= */
public static final String METHOD_REQMOD = "REQMOD";
public static final String METHOD_RESPMOD = "RESPMOD";
public static final String METHOD_OPTIONS = "OPTIONS";
/* =============================================================
* Constants defining http header names
* ============================================================= */
public static final String HOST = "Host";
public static final String USER_AGENT = "User-Agent";
public static final String CONNECTION = "Connection";
public static final String DATE = "Date";
public static final String SERVER = "Server";
public static final String ISTAG = "ISTAG";
public static final String METHODS = "Methods";
public static final String ALLOW = "Allow";
public static final String ENCAPSULATED = "Encapsulated";
public static final String MAX_CONNECTIONS = "Max-Connections";
public static final String OPTIONS_TTL = "Options-TTL";
public static final String SERVICE = "Service";
public static final String SERVICE_ID = "Service-ID";
public static final String PREVIEW = "Preview";
public static final String TRANSFER_PREVIEW = "Transfer-Preview";
public static final String TRANSFER_IGNORE = "Transfer-Ignore";
public static final String TRANSFER_COMPLETE = "Transfer-Complete";
public static final String X_YACY_KEEP_ALIVE_REQUEST_COUNT = "X-Keep-Alive-Request-Count";
/* =============================================================
* defining default icap status messages
* ============================================================= */
public static final HashMap<String, String> icap1_0 = new HashMap<String, String>();
static {
// (1yz) Informational codes
icap1_0.put("100","Continue after ICAP preview");
// (2yz) Success codes:
icap1_0.put("200","OK");
icap1_0.put("204","No modifications needed");
// (4yz) Client error codes:
icap1_0.put("400","Bad request");
icap1_0.put("404","ICAP Service not found");
icap1_0.put("405","Method not allowed for service");
icap1_0.put("408","Request timeout");
// (5yz) Server error codes:
icap1_0.put("500","Server error");
icap1_0.put("501","Method not implemented");
icap1_0.put("502","Bad Gateway");
icap1_0.put("503","Service overloaded");
icap1_0.put("505","ICAP version not supported by server");
}
/* PROPERTIES: General properties */
public static final String CONNECTION_PROP_ICAP_VER = "ICAP";
public static final String CONNECTION_PROP_HOST = "HOST";
public static final String CONNECTION_PROP_PATH = "PATH";
public static final String CONNECTION_PROP_EXT = "EXT";
public static final String CONNECTION_PROP_METHOD = "METHOD";
public static final String CONNECTION_PROP_REQUESTLINE = "REQUESTLINE";
public static final String CONNECTION_PROP_CLIENTIP = "CLIENTIP";
public static final String CONNECTION_PROP_URL = "URL";
public static final String CONNECTION_PROP_ARGS = "ARGS";
public static final String CONNECTION_PROP_PERSISTENT = "PERSISTENT";
public static final String CONNECTION_PROP_KEEP_ALIVE_COUNT = "KEEP-ALIVE_COUNT";
private static final Collator insensitiveCollator = Collator.getInstance(Locale.US);
static {
insensitiveCollator.setStrength(Collator.SECONDARY);
insensitiveCollator.setDecomposition(Collator.NO_DECOMPOSITION);
}
public icapHeader() {
super(insensitiveCollator);
}
public boolean allow(final int statusCode) {
if (!super.containsKey("Allow")) return false;
final String allow = get("Allow");
return (allow.indexOf(Integer.toString(statusCode))!=-1);
}
// to make the occurrence of multiple keys possible, we add them using a counter
public String add(final String key, final String value) {
final int c = keyCount(key);
if (c == 0) return put(key, value);
return put("*" + key + "-" + c, value);
}
public int keyCount(final String key) {
if (!(containsKey(key))) return 0;
int c = 1;
while (containsKey("*" + key + "-" + c)) c++;
return c;
}
// a convenience method to access the map with fail-over defaults
public Object get(final Object key, final Object dflt) {
final Object result = get(key);
if (result == null) return dflt;
return result;
}
// return multiple results
public Object getSingle(final Object key, final int count) {
if (count == 0) return get(key, null);
return get("*" + key + "-" + count, null);
}
public StringBuilder toHeaderString(final String icapVersion, final int icapStatusCode, String icapStatusText) {
if ((icapStatusText == null)||(icapStatusText.length()==0)) {
if (icapVersion.equals("ICAP/1.0") && icapHeader.icap1_0.containsKey(Integer.toString(icapStatusCode)))
icapStatusText = icapHeader.icap1_0.get(Integer.toString(icapStatusCode));
}
final StringBuilder theHeader = new StringBuilder();
// write status line
theHeader.append(icapVersion).append(" ")
.append(Integer.toString(icapStatusCode)).append(" ")
.append(icapStatusText).append("\r\n");
// write header
final Iterator<String> i = keySet().iterator();
String key;
char tag;
int count;
while (i.hasNext()) {
key = i.next();
tag = key.charAt(0);
if ((tag != '*') && (tag != '#')) { // '#' in key is reserved for proxy attributes as artificial header values
count = keyCount(key);
for (int j = 0; j < count; j++) {
theHeader.append(key).append(": ").append((String) getSingle(key, j)).append("\r\n");
}
}
}
// end header
theHeader.append("\r\n");
return theHeader;
}
public static Properties parseRequestLine(final String cmd, String s, final Properties prop, final String virtualHost) {
// reset property from previous run
prop.clear();
// storing informations about the request
prop.setProperty(CONNECTION_PROP_METHOD, cmd);
prop.setProperty(CONNECTION_PROP_REQUESTLINE,cmd + " " + s);
// this parses a whole URL
if (s.length() == 0) {
prop.setProperty(CONNECTION_PROP_HOST, virtualHost);
prop.setProperty(CONNECTION_PROP_PATH, "/");
prop.setProperty(CONNECTION_PROP_ICAP_VER, "ICAP/1.0");
prop.setProperty(CONNECTION_PROP_EXT, "");
return prop;
}
// store the version propery "ICAP" and cut the query at both ends
int sep = s.indexOf(" ");
if (sep >= 0) {
// ICAP version is given
prop.setProperty(CONNECTION_PROP_ICAP_VER, s.substring(sep + 1).trim());
s = s.substring(0, sep).trim(); // cut off ICAP version mark
} else {
// ICAP version is not given, it will be treated as ver 0.9
prop.setProperty(CONNECTION_PROP_ICAP_VER, "ICAP/1.0");
}
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);
}
prop.setProperty(CONNECTION_PROP_URL, s); // store URL
if (argsString.length() != 0) prop.setProperty(CONNECTION_PROP_ARGS, argsString); // store arguments in original form
// finally find host string
if (s.toUpperCase().startsWith("ICAP://")) {
// 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
prop.setProperty(CONNECTION_PROP_HOST, virtualHost);
prop.setProperty(CONNECTION_PROP_PATH, "/" + s);
} else {
// THIS IS THE "GOOD" CASE
// a perfect formulated url
prop.setProperty(CONNECTION_PROP_HOST, s.substring(0, sep));
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
prop.setProperty(CONNECTION_PROP_HOST, virtualHost);
prop.setProperty(CONNECTION_PROP_PATH, s);
} else {
// the client 'forgot' to set a leading '/'
// this is the same case as above, with some lazyness
prop.setProperty(CONNECTION_PROP_HOST, virtualHost);
prop.setProperty(CONNECTION_PROP_PATH, "/" + s);
}
}
return prop;
}
public static icapHeader readHeader(final Properties prop, final serverCore.Session theSession) {
// reading all headers
final icapHeader header = new icapHeader();
int p;
String line;
while ((line = theSession.readLineAsString()) != 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());
}
}
return header;
}
}