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.
287 lines
12 KiB
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 StringBuffer 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 StringBuffer theHeader = new StringBuffer();
|
|
|
|
// 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;
|
|
}
|
|
}
|