0001: // httpHeader.java
0002: // -----------------------
0003: // (C) by Michael Peter Christen; mc@anomic.de
0004: // first published on http://www.anomic.de
0005: // Frankfurt, Germany, 2004
0006: //
0007: // last major change: $LastChangedDate: 2008-01-29 10:12:48 +0000 (Di, 29 Jan 2008) $ by $LastChangedBy: orbiter $
0008: // Revision: $LastChangedRevision: 4414 $
0009: //
0010: // This program is free software; you can redistribute it and/or modify
0011: // it under the terms of the GNU General Public License as published by
0012: // the Free Software Foundation; either version 2 of the License, or
0013: // (at your option) any later version.
0014: //
0015: // This program is distributed in the hope that it will be useful,
0016: // but WITHOUT ANY WARRANTY; without even the implied warranty of
0017: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0018: // GNU General Public License for more details.
0019: //
0020: // You should have received a copy of the GNU General Public License
0021: // along with this program; if not, write to the Free Software
0022: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0023: //
0024: // Using this software in any meaning (reading, learning, copying, compiling,
0025: // running) means that you agree that the Author(s) is (are) not responsible
0026: // for cost, loss of data or any harm that may be caused directly or indirectly
0027: // by usage of this softare or this documentation. The usage of this software
0028: // is on your own risk. The installation and usage (starting/running) of this
0029: // software may allow other people or application to access your computer and
0030: // any attached devices and is highly dependent on the configuration of the
0031: // software which must be done by the user of the software; the author(s) is
0032: // (are) also not responsible for proper configuration and usage of the
0033: // software, even if provoked by documentation provided together with
0034: // the software.
0035: //
0036: // Any changes to this file according to the GPL as documented in the file
0037: // gpl.txt aside this file in the shipment you received can be done to the
0038: // lines that follows this copyright notice here, but changes must not be
0039: // done inside the copyright notive above. A re-distribution must contain
0040: // the intact and unchanged copyright notice.
0041: // Contributions and changes to the program code must be marked as such.
0042:
0043: /*
0044: Documentation:
0045: this class implements a key-value mapping, as a hashtable
0046: The difference to ordinary hashtable implementations is that the
0047: keys are not compared by the equal() method, but are always
0048: treated as string and compared as
0049: key.uppercase().equal(.uppercase(comparator))
0050: You use this class by first creation of a static HashMap
0051: that then is used a the reverse mapping cache for every new
0052: instance of this class.
0053: */
0054:
0055: package de.anomic.http;
0056:
0057: import java.io.BufferedReader;
0058: import java.io.File;
0059: import java.io.FileOutputStream;
0060: import java.io.FileReader;
0061: import java.io.IOException;
0062: import java.net.MalformedURLException;
0063: import java.text.Collator;
0064: import java.util.Date;
0065: import java.util.HashMap;
0066: import java.util.Iterator;
0067: import java.util.Locale;
0068: import java.util.Map;
0069: import java.util.Properties;
0070: import java.util.TreeMap;
0071: import java.util.Vector;
0072:
0073: import de.anomic.server.serverCore;
0074: import de.anomic.server.serverDate;
0075: import de.anomic.yacy.yacyURL;
0076:
0077: public final class httpHeader extends TreeMap<String, String> implements
0078: Map<String, String> {
0079:
0080: private static final long serialVersionUID = 17L;
0081:
0082: public static final String DEFAULT_CHARSET = "ISO-8859-1";
0083:
0084: /* =============================================================
0085: * Constants defining http versions
0086: * ============================================================= */
0087: public static final String HTTP_VERSION_0_9 = "HTTP/0.9";
0088: public static final String HTTP_VERSION_1_0 = "HTTP/1.0";
0089: public static final String HTTP_VERSION_1_1 = "HTTP/1.1";
0090:
0091: /* =============================================================
0092: * Constants defining http header names
0093: * ============================================================= */
0094: public static final String ACCEPT = "Accept";
0095: public static final String ACCEPT_CHARSET = "Accept-Charset";
0096: public static final String ACCEPT_LANGUAGE = "Accept-Language";
0097:
0098: public static final String HOST = "Host";
0099:
0100: public static final String CONNECTION = "Connection";
0101: public static final String PROXY_CONNECTION = "Proxy-Connection";
0102: public static final String KEEP_ALIVE = "Keep-Alive";
0103:
0104: public static final String REFERER = "Referer";
0105: public static final String USER_AGENT = "User-Agent";
0106:
0107: public static final String AUTHORIZATION = "Authorization";
0108: public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
0109: public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
0110: public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate";
0111:
0112: public static final String DATE = "Date";
0113: public static final String SERVER = "Server";
0114:
0115: public static final String CONTENT_LENGTH = "Content-Length";
0116: public static final String CONTENT_TYPE = "Content-Type";
0117: public static final String CONTENT_MD5 = "Content-MD5";
0118: public static final String CONTENT_LOCATION = "Content-Location";
0119:
0120: public static final String SET_COOKIE = "Set-Cookie";
0121: public static final String SET_COOKIE2 = "Set-Cookie2";
0122: public static final String COOKIE = "Cookie";
0123: public static final String EXPIRES = "Expires";
0124:
0125: public static final String ACCEPT_ENCODING = "Accept-Encoding";
0126: public static final String CONTENT_ENCODING = "Content-Encoding";
0127: public static final String TRANSFER_ENCODING = "Transfer-Encoding";
0128:
0129: public static final String ACCEPT_RANGES = "Accept-Ranges";
0130: public static final String CONTENT_RANGE = "Content-Range";
0131: public static final String RANGE = "Range";
0132: public static final String IF_RANGE = "If-Range";
0133:
0134: public static final String PRAGMA = "Pragma";
0135: public static final String CACHE_CONTROL = "Cache-Control";
0136: public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
0137: public static final String LAST_MODIFIED = "Last-modified";
0138:
0139: public static final String LOCATION = "Location";
0140: public static final String ETAG = "ETag";
0141: public static final String VIA = "Via";
0142:
0143: public static final String X_CACHE = "X-Cache";
0144: public static final String X_CACHE_LOOKUP = "X-Cache-Lookup";
0145: public static final String X_FORWARDED_FOR = "X-Forwarded-For";
0146:
0147: public static final String X_YACY_KEEP_ALIVE_REQUEST_COUNT = "X-Keep-Alive-Request-Count";
0148: public static final String X_YACY_ORIGINAL_REQUEST_LINE = "X-Original-Request-Line";
0149: public static final String X_YACY_PREVIOUS_REQUEST_LINE = "X-Previous-Request-Line";
0150:
0151: public static final String X_YACY_INDEX_CONTROL = "X-YACY-Index-Control";
0152:
0153: public static final String UPGRADE = "Upgrade";
0154: public static final String TE = "TE";
0155:
0156: /* =============================================================
0157: * Constants for content-encodings
0158: * ============================================================= */
0159: public static final String CONTENT_ENCODING_GZIP = "gzip";
0160:
0161: /* =============================================================
0162: * Constants defining http methods
0163: * ============================================================= */
0164: public static final String METHOD_GET = "GET";
0165: public static final String METHOD_HEAD = "HEAD";
0166: public static final String METHOD_POST = "POST";
0167: public static final String METHOD_CONNECT = "CONNECT";
0168:
0169: /* =============================================================
0170: * defining default http status messages
0171: * ============================================================= */
0172: public static final HashMap<String, String> http0_9 = new HashMap<String, String>();
0173: public static final HashMap<String, String> http1_0 = new HashMap<String, String>();
0174: static {
0175: http1_0.putAll(http0_9);
0176: http1_0.put("200", "OK");
0177: http1_0.put("201", "Created");
0178: http1_0.put("202", "Accepted");
0179: http1_0.put("204", "No Content");
0180: http1_0.put("300", "Multiple Choices");
0181: http1_0.put("301", "Moved Permanently");
0182: http1_0.put("302", "Moved Temporarily");
0183: http1_0.put("304", "Not Modified");
0184: http1_0.put("400", "Bad Request");
0185: http1_0.put("401", "Unauthorized");
0186: http1_0.put("403", "Forbidden");
0187: http1_0.put("404", "Not Found");
0188: http1_0.put("500", "Internal Server Error");
0189: http1_0.put("501", "Not Implemented");
0190: http1_0.put("502", "Bad Gateway");
0191: http1_0.put("503", "Service Unavailable");
0192: }
0193: public static final HashMap<String, String> http1_1 = new HashMap<String, String>();
0194: static {
0195: http1_1.putAll(http1_0);
0196: http1_1.put("100", "Continue");
0197: http1_1.put("101", "Switching Protocols");
0198: http1_1.put("203", "Non-Authoritative Information");
0199: http1_1.put("205", "Reset Content");
0200: http1_1.put("206", "Partial Content");
0201: http1_1.put("300", "Multiple Choices");
0202: http1_1.put("303", "See Other");
0203: http1_1.put("305", "Use Proxy");
0204: http1_1.put("307", "Temporary Redirect");
0205: http1_1.put("402", "Payment Required");
0206: http1_1.put("405", "Method Not Allowed");
0207: http1_1.put("406", "Not Acceptable");
0208: http1_1.put("407", "Proxy Authentication Required");
0209: http1_1.put("408", "Request Time-out");
0210: http1_1.put("409", "Conflict");
0211: http1_1.put("410", "Gone");
0212: http1_1.put("411", "Length Required");
0213: http1_1.put("412", "Precondition Failed");
0214: http1_1.put("413", "Request Entity Too Large");
0215: http1_1.put("414", "Request-URI Too Large");
0216: http1_1.put("415", "Unsupported Media Type");
0217: http1_1.put("416", "Requested range not satisfiable");
0218: http1_1.put("417", "Expectation Failed");
0219: http1_1.put("504", "Gateway Time-out");
0220: http1_1.put("505", "HTTP Version not supported");
0221: }
0222:
0223: /* PROPERTIES: General properties */
0224: public static final String CONNECTION_PROP_HTTP_VER = "HTTP";
0225: public static final String CONNECTION_PROP_HOST = "HOST";
0226: public static final String CONNECTION_PROP_USER = "USER";
0227: public static final String CONNECTION_PROP_METHOD = "METHOD";
0228: public static final String CONNECTION_PROP_PATH = "PATH";
0229: public static final String CONNECTION_PROP_EXT = "EXT";
0230: public static final String CONNECTION_PROP_URL = "URL";
0231: public static final String CONNECTION_PROP_ARGS = "ARGS";
0232: public static final String CONNECTION_PROP_CLIENTIP = "CLIENTIP";
0233: public static final String CONNECTION_PROP_PERSISTENT = "PERSISTENT";
0234: public static final String CONNECTION_PROP_KEEP_ALIVE_COUNT = "KEEP-ALIVE_COUNT";
0235: public static final String CONNECTION_PROP_REQUESTLINE = "REQUESTLINE";
0236: public static final String CONNECTION_PROP_PREV_REQUESTLINE = "PREVREQUESTLINE";
0237: public static final String CONNECTION_PROP_REQUEST_START = "REQUEST_START";
0238: public static final String CONNECTION_PROP_REQUEST_END = "REQUEST_END";
0239: //public static final String CONNECTION_PROP_INPUTSTREAM = "INPUTSTREAM";
0240: //public static final String CONNECTION_PROP_OUTPUTSTREAM = "OUTPUTSTREAM";
0241:
0242: /* PROPERTIES: Client -> Proxy */
0243: public static final String CONNECTION_PROP_CLIENT_REQUEST_HEADER = "CLIENT_REQUEST_HEADER";
0244:
0245: /* PROPERTIES: Proxy -> Client */
0246: public static final String CONNECTION_PROP_PROXY_RESPOND_CODE = "PROXY_RESPOND_CODE";
0247: public static final String CONNECTION_PROP_PROXY_RESPOND_STATUS = "PROXY_RESPOND_STATUS";
0248: public static final String CONNECTION_PROP_PROXY_RESPOND_HEADER = "PROXY_RESPOND_HEADER";
0249: public static final String CONNECTION_PROP_PROXY_RESPOND_SIZE = "PROXY_REQUEST_SIZE";
0250:
0251: private final HashMap<String, String> reverseMappingCache;
0252:
0253: private static final Collator insensitiveCollator = Collator
0254: .getInstance(Locale.US);
0255: static {
0256: insensitiveCollator.setStrength(Collator.SECONDARY);
0257: insensitiveCollator.setDecomposition(Collator.NO_DECOMPOSITION);
0258: }
0259:
0260: public httpHeader() {
0261: this (null);
0262: }
0263:
0264: public httpHeader(HashMap<String, String> reverseMappingCache) {
0265: // this creates a new TreeMap with a case insensitive mapping
0266: // to provide a put-method that translates given keys into their
0267: // 'proper' appearance, a translation cache is needed.
0268: // upon instantiation, such a mapping cache can be handed over
0269: // If the reverseMappingCache is null, none is used
0270: super ((Collator) insensitiveCollator.clone());
0271: this .reverseMappingCache = reverseMappingCache;
0272: }
0273:
0274: public httpHeader(HashMap<String, String> reverseMappingCache,
0275: File f) throws IOException {
0276: // creates also a case insensitive map and loads it initially
0277: // with some values
0278: super ((Collator) insensitiveCollator.clone());
0279: this .reverseMappingCache = reverseMappingCache;
0280:
0281: // load with data
0282: BufferedReader br = new BufferedReader(new FileReader(f));
0283: String line;
0284: int pos;
0285: while ((line = br.readLine()) != null) {
0286: pos = line.indexOf("=");
0287: if (pos >= 0)
0288: put(line.substring(0, pos), line.substring(pos + 1));
0289: }
0290: br.close();
0291: }
0292:
0293: public httpHeader(HashMap<String, String> reverseMappingCache,
0294: Map<String, String> othermap) {
0295: // creates a case insensitive map from another map
0296: super ((Collator) insensitiveCollator.clone());
0297: this .reverseMappingCache = reverseMappingCache;
0298:
0299: // load with data
0300: if (othermap != null)
0301: this .putAll(othermap);
0302: }
0303:
0304: // we override the put method to make use of the reverseMappingCache
0305: public String put(String key, String value) {
0306: String upperK = key.toUpperCase();
0307:
0308: if (reverseMappingCache == null) {
0309: return super .put(key, value);
0310: }
0311:
0312: if (reverseMappingCache.containsKey(upperK)) {
0313: // we put in the value using the reverse mapping
0314: return super .put(reverseMappingCache.get(upperK), value);
0315: }
0316:
0317: // we put in without a cached key and store the key afterwards
0318: String r = super .put(key, value);
0319: reverseMappingCache.put(upperK, key);
0320: return r;
0321: }
0322:
0323: // to make the occurrence of multiple keys possible, we add them using a counter
0324: public String add(String key, String value) {
0325: int c = keyCount(key);
0326: if (c == 0)
0327: return put(key, value);
0328: return put("*" + key + "-" + c, value);
0329: }
0330:
0331: public int keyCount(String key) {
0332: if (!(containsKey(key)))
0333: return 0;
0334: int c = 1;
0335: while (containsKey("*" + key + "-" + c))
0336: c++;
0337: return c;
0338: }
0339:
0340: // a convenience method to access the map with fail-over defaults
0341: public Object get(Object key, Object dflt) {
0342: Object result = get(key);
0343: if (result == null)
0344: return dflt;
0345: return result;
0346: }
0347:
0348: // return multiple results
0349: public Object getSingle(Object key, int count) {
0350: if (count == 0)
0351: return get(key, null);
0352: return get("*" + key + "-" + count, null);
0353: }
0354:
0355: public Object[] getMultiple(String key) {
0356: int count = keyCount(key);
0357: Object[] result = new Object[count];
0358: for (int i = 0; i < count; i++)
0359: result[i] = getSingle(key, i);
0360: return result;
0361: }
0362:
0363: // convenience methods for storing and loading to a file system
0364: public void store(File f) throws IOException {
0365: FileOutputStream fos = null;
0366: try {
0367: fos = new FileOutputStream(f);
0368: Iterator<String> i = keySet().iterator();
0369: String key, value;
0370: while (i.hasNext()) {
0371: key = i.next();
0372: value = (String) get(key);
0373: fos.write((key + "=" + value + "\r\n").getBytes());
0374: }
0375: fos.flush();
0376: } finally {
0377: if (fos != null)
0378: try {
0379: fos.close();
0380: } catch (Exception e) {
0381: }
0382: }
0383: }
0384:
0385: public String toString() {
0386: return super .toString();
0387: }
0388:
0389: /*
0390: * example header
0391: Connection=close
0392: Content-Encoding=gzip
0393: Content-Length=7281
0394: Content-Type=text/html
0395: Date=Mon, 05 Jan 2004 11:55:10 GMT
0396: Server=Apache/1.3.26
0397: */
0398:
0399: private Date headerDate(String kind) {
0400: if (containsKey(kind)) {
0401: Date parsedDate = serverDate
0402: .parseHTTPDate((String) get(kind));
0403: if (parsedDate == null)
0404: parsedDate = new Date();
0405: return parsedDate;
0406: }
0407: return null;
0408: }
0409:
0410: public String mime() {
0411: return (String) get(httpHeader.CONTENT_TYPE,
0412: "application/octet-stream");
0413: }
0414:
0415: public String getCharacterEncoding() {
0416: String mimeType = mime();
0417: return extractCharsetFromMimetyeHeader(mimeType);
0418: }
0419:
0420: public static String extractCharsetFromMimetyeHeader(String mimeType) {
0421: if (mimeType == null)
0422: return null;
0423:
0424: String[] parts = mimeType.split(";");
0425: if (parts == null || parts.length <= 1)
0426: return null;
0427:
0428: for (int i = 1; i < parts.length; i++) {
0429: String param = parts[i].trim();
0430: if (param.startsWith("charset=")) {
0431: String charset = param.substring("charset=".length())
0432: .trim();
0433: if (charset.startsWith("\"") || charset.startsWith("'"))
0434: charset = charset.substring(1);
0435: if (charset.endsWith("\"") || charset.endsWith("'"))
0436: charset = charset
0437: .substring(0, charset.length() - 1);
0438: return charset.trim();
0439: }
0440: }
0441:
0442: return null;
0443: }
0444:
0445: public Date date() {
0446: return headerDate(httpHeader.DATE);
0447: }
0448:
0449: public Date expires() {
0450: return headerDate(httpHeader.EXPIRES);
0451: }
0452:
0453: public Date lastModified() {
0454: return headerDate(httpHeader.LAST_MODIFIED);
0455: }
0456:
0457: public Date ifModifiedSince() {
0458: return headerDate(httpHeader.IF_MODIFIED_SINCE);
0459: }
0460:
0461: public Object ifRange() {
0462: if (containsKey(httpHeader.IF_RANGE)) {
0463: Date rangeDate = serverDate
0464: .parseHTTPDate((String) get(httpHeader.IF_RANGE));
0465: if (rangeDate != null)
0466: return rangeDate;
0467:
0468: return get(httpHeader.IF_RANGE);
0469: }
0470: return null;
0471: }
0472:
0473: public String userAgent() {
0474: return (String) get(USER_AGENT, "");
0475: }
0476:
0477: public long age() {
0478: Date lm = lastModified();
0479: Date sd = date();
0480: if (lm == null)
0481: return Long.MAX_VALUE;
0482: return ((sd == null) ? new Date() : sd).getTime()
0483: - lm.getTime();
0484: }
0485:
0486: public long contentLength() {
0487: if (containsKey(httpHeader.CONTENT_LENGTH)) {
0488: try {
0489: return Long
0490: .parseLong((String) get(httpHeader.CONTENT_LENGTH));
0491: } catch (NumberFormatException e) {
0492: return -1;
0493: }
0494: }
0495: return -1;
0496: }
0497:
0498: public boolean acceptGzip() {
0499: return ((containsKey(httpHeader.ACCEPT_ENCODING)) && (((String) get(httpHeader.ACCEPT_ENCODING))
0500: .toUpperCase().indexOf("GZIP")) != -1);
0501: }
0502:
0503: public boolean gzip() {
0504: return ((containsKey(httpHeader.CONTENT_ENCODING)) && (((String) get(httpHeader.CONTENT_ENCODING))
0505: .toUpperCase().startsWith("GZIP")));
0506: }
0507:
0508: public static Object[] parseResponseLine(String respLine) {
0509:
0510: if ((respLine == null) || (respLine.length() == 0)) {
0511: return new Object[] { "HTTP/1.0", new Integer(500),
0512: "status line parse error" };
0513: }
0514:
0515: int p = respLine.indexOf(" ");
0516: if (p < 0) {
0517: return new Object[] { "HTTP/1.0", new Integer(500),
0518: "status line parse error" };
0519: }
0520:
0521: String httpVer, status, statusText;
0522: Integer statusCode;
0523:
0524: // the http version reported by the server
0525: httpVer = respLine.substring(0, p);
0526:
0527: // Status of the request, e.g. "200 OK"
0528: status = respLine.substring(p + 1).trim(); // the status code plus reason-phrase
0529:
0530: // splitting the status into statuscode and statustext
0531: p = status.indexOf(" ");
0532: try {
0533: statusCode = Integer.valueOf((p < 0) ? status.trim()
0534: : status.substring(0, p).trim());
0535: statusText = (p < 0) ? "" : status.substring(p + 1).trim();
0536: } catch (Exception e) {
0537: statusCode = new Integer(500);
0538: statusText = status;
0539: }
0540:
0541: return new Object[] { httpVer, statusCode, statusText };
0542: }
0543:
0544: public static Properties parseRequestLine(String s,
0545: Properties prop, String virtualHost) {
0546: int p = s.indexOf(" ");
0547: if (p >= 0) {
0548: String cmd = s.substring(0, p);
0549: String args = s.substring(p + 1);
0550: return parseRequestLine(cmd, args, prop, virtualHost);
0551: }
0552: return prop;
0553: }
0554:
0555: public static Properties parseRequestLine(String cmd, String args,
0556: Properties prop, String virtualHost) {
0557:
0558: // getting the last request line for debugging purposes
0559: String prevRequestLine = prop
0560: .containsKey(CONNECTION_PROP_REQUESTLINE) ? prop
0561: .getProperty(CONNECTION_PROP_REQUESTLINE) : "";
0562:
0563: // reset property from previous run
0564: prop.clear();
0565:
0566: // storing informations about the request
0567: prop.setProperty(CONNECTION_PROP_METHOD, cmd);
0568: prop.setProperty(CONNECTION_PROP_REQUESTLINE, cmd + " " + args);
0569: prop.setProperty(CONNECTION_PROP_PREV_REQUESTLINE,
0570: prevRequestLine);
0571:
0572: // this parses a whole URL
0573: if (args.length() == 0) {
0574: prop.setProperty(CONNECTION_PROP_HOST, virtualHost);
0575: prop.setProperty(CONNECTION_PROP_PATH, "/");
0576: prop
0577: .setProperty(CONNECTION_PROP_HTTP_VER,
0578: HTTP_VERSION_0_9);
0579: prop.setProperty(CONNECTION_PROP_EXT, "");
0580: return prop;
0581: }
0582:
0583: // store the version propery "HTTP" and cut the query at both ends
0584: int sep = args.lastIndexOf(" ");
0585: if ((sep >= 0)
0586: && (args.substring(sep + 1).toLowerCase()
0587: .startsWith("http/"))) {
0588: // HTTP version is given
0589: prop.setProperty(CONNECTION_PROP_HTTP_VER, args.substring(
0590: sep + 1).trim());
0591: args = args.substring(0, sep).trim(); // cut off HTTP version mark
0592: } else {
0593: // HTTP version is not given, it will be treated as ver 0.9
0594: prop
0595: .setProperty(CONNECTION_PROP_HTTP_VER,
0596: HTTP_VERSION_0_9);
0597: }
0598:
0599: // replacing spaces in the url string correctly
0600: args = args.replaceAll(" ", "%20");
0601:
0602: // properties of the query are stored with the prefix "&"
0603: // additionally, the values URL and ARGC are computed
0604:
0605: String argsString = "";
0606: sep = args.indexOf("?");
0607: if (sep >= 0) {
0608: // there are values attached to the query string
0609: argsString = args.substring(sep + 1); // cut head from tail of query
0610: args = args.substring(0, sep);
0611: }
0612: prop.setProperty(CONNECTION_PROP_URL, args); // store URL
0613: //System.out.println("HTTPD: ARGS=" + argsString);
0614: if (argsString.length() != 0)
0615: prop.setProperty(CONNECTION_PROP_ARGS, argsString); // store arguments in original form
0616:
0617: // finally find host string
0618: String path;
0619: if (args.toUpperCase().startsWith("HTTP://")) {
0620: // a host was given. extract it and set path
0621: args = args.substring(7);
0622: sep = args.indexOf("/");
0623: if (sep < 0) {
0624: // this is a malformed url, something like
0625: // http://index.html
0626: // we are lazy and guess that it means
0627: // /index.html
0628: // which is a localhost access to the file servlet
0629: prop.setProperty(CONNECTION_PROP_HOST, args);
0630: path = "/";
0631: } else {
0632: // THIS IS THE "GOOD" CASE
0633: // a perfect formulated url
0634: String dstHostSocket = args.substring(0, sep);
0635: prop.setProperty(CONNECTION_PROP_HOST, (httpd
0636: .isThisHostName(dstHostSocket) ? virtualHost
0637: : dstHostSocket));
0638: path = args.substring(sep); // yes, including beginning "/"
0639: }
0640: } else {
0641: // no host in url. set path
0642: if (args.startsWith("/")) {
0643: // thats also fine, its a perfect localhost access
0644: // in this case, we simulate a
0645: // http://localhost/s
0646: // access by setting a virtual host
0647: prop.setProperty(CONNECTION_PROP_HOST, virtualHost);
0648: path = args;
0649: } else {
0650: // the client 'forgot' to set a leading '/'
0651: // this is the same case as above, with some lazyness
0652: prop.setProperty(CONNECTION_PROP_HOST, virtualHost);
0653: path = "/" + args;
0654: }
0655: }
0656: prop.setProperty(CONNECTION_PROP_PATH, path);
0657:
0658: // find out file extension (we already stripped ?-parameters from args)
0659: String ext = ""; // default when no file extension
0660: sep = path.lastIndexOf(".");
0661: if (sep >= 0) {
0662: int ancpos = path.indexOf("#", sep + 1);
0663: if (ancpos >= sep) {
0664: // ex: /foo/bar.html#xy => html
0665: ext = path.substring(sep + 1, ancpos).toLowerCase();
0666: } else {
0667: // ex: /foo/bar.php => php
0668: ext = path.substring(sep + 1).toLowerCase();
0669: }
0670: }
0671: prop.setProperty(CONNECTION_PROP_EXT, ext);
0672:
0673: return prop;
0674: }
0675:
0676: public static boolean supportChunkedEncoding(Properties conProp) {
0677: // getting the http version of the client
0678: String httpVer = conProp.getProperty(CONNECTION_PROP_HTTP_VER);
0679:
0680: // only clients with http version 1.1 supports chunk
0681: return !(httpVer.equals(HTTP_VERSION_0_9) || httpVer
0682: .equals(HTTP_VERSION_1_0));
0683: }
0684:
0685: /**
0686: * Reading http headers from a reader class and building up a httpHeader object
0687: * @param reader the {@link BufferedReader} that is used to read the http header lines
0688: * @return a {@link httpHeader}-Object containing all parsed headers
0689: * @throws IOException
0690: */
0691: public static httpHeader readHttpHeader(BufferedReader reader)
0692: throws IOException {
0693: // reading all request headers
0694: httpHeader httpHeader = new httpHeader(
0695: httpd.reverseMappingCache);
0696: int p;
0697: String line;
0698: while ((line = reader.readLine()) != null) {
0699: if (line.length() == 0)
0700: break;
0701: if ((p = line.indexOf(":")) >= 0) {
0702: // store a property
0703: httpHeader.add(line.substring(0, p).trim(), line
0704: .substring(p + 1).trim());
0705: }
0706: }
0707: return httpHeader;
0708: }
0709:
0710: public static httpHeader readHeader(Properties prop,
0711: serverCore.Session theSession) throws IOException {
0712:
0713: // reading all headers
0714: httpHeader header = new httpHeader(httpd.reverseMappingCache);
0715: int p;
0716: String line;
0717: while ((line = theSession.readLineAsString()) != null) {
0718: if (line.length() == 0)
0719: break; // this seperates the header of the HTTP request from the body
0720: // parse the header line: a property seperated with the ':' sign
0721: if ((p = line.indexOf(":")) >= 0) {
0722: // store a property
0723: header.add(line.substring(0, p).trim(), line.substring(
0724: p + 1).trim());
0725: }
0726: }
0727:
0728: /*
0729: * doing some header validation here ...
0730: */
0731: String httpVersion = prop.getProperty(
0732: httpHeader.CONNECTION_PROP_HTTP_VER, "HTTP/0.9");
0733: if (httpVersion.equals("HTTP/1.1")
0734: && !header.containsKey(httpHeader.HOST)) {
0735: // the HTTP/1.1 specification requires that an HTTP/1.1 server must reject any
0736: // HTTP/1.1 message that does not contain a Host header.
0737: httpd.sendRespondError(prop, theSession.out, 0, 400, null,
0738: null, null);
0739: throw new IOException("400 Bad request");
0740: }
0741:
0742: return header;
0743: }
0744:
0745: public StringBuffer toHeaderString(String httpVersion,
0746: int httpStatusCode, String httpStatusText) {
0747: // creating a new buffer to store the header as string
0748: StringBuffer theHeader = new StringBuffer();
0749:
0750: // generating the header string
0751: this .toHeaderString(httpVersion, httpStatusCode,
0752: httpStatusText, theHeader);
0753:
0754: // returning the result
0755: return theHeader;
0756: }
0757:
0758: public void toHeaderString(String httpVersion, int httpStatusCode,
0759: String httpStatusText, StringBuffer theHeader) {
0760:
0761: if (theHeader == null)
0762: throw new IllegalArgumentException();
0763:
0764: // setting the http version if it was not already set
0765: if (httpVersion == null)
0766: httpVersion = "HTTP/1.0";
0767:
0768: // setting the status text if it was not already set
0769: if ((httpStatusText == null) || (httpStatusText.length() == 0)) {
0770: if (httpVersion.equals("HTTP/1.0")
0771: && httpHeader.http1_0.containsKey(Integer
0772: .toString(httpStatusCode)))
0773: httpStatusText = httpHeader.http1_0.get(Integer
0774: .toString(httpStatusCode));
0775: else if (httpVersion.equals("HTTP/1.1")
0776: && httpHeader.http1_1.containsKey(Integer
0777: .toString(httpStatusCode)))
0778: httpStatusText = httpHeader.http1_1.get(Integer
0779: .toString(httpStatusCode));
0780: else
0781: httpStatusText = "Unknown";
0782: }
0783:
0784: // write status line
0785: theHeader.append(httpVersion).append(" ").append(
0786: Integer.toString(httpStatusCode)).append(" ").append(
0787: httpStatusText).append("\r\n");
0788:
0789: // write header
0790: Iterator<String> i = keySet().iterator();
0791: String key;
0792: char tag;
0793: int count;
0794: while (i.hasNext()) {
0795: key = i.next();
0796: tag = key.charAt(0);
0797: if ((tag != '*') && (tag != '#')) { // '#' in key is reserved for proxy attributes as artificial header values
0798: count = keyCount(key);
0799: for (int j = 0; j < count; j++) {
0800: theHeader.append(key).append(": ").append(
0801: (String) getSingle(key, j)).append("\r\n");
0802: }
0803: }
0804: }
0805: // end header
0806: theHeader.append("\r\n");
0807: }
0808:
0809: public static yacyURL getRequestURL(Properties conProp)
0810: throws MalformedURLException {
0811: String host = conProp
0812: .getProperty(httpHeader.CONNECTION_PROP_HOST);
0813: String path = conProp
0814: .getProperty(httpHeader.CONNECTION_PROP_PATH); // always starts with leading '/'
0815: String args = conProp
0816: .getProperty(httpHeader.CONNECTION_PROP_ARGS); // may be null if no args were given
0817: //String ip = conProp.getProperty(httpHeader.CONNECTION_PROP_CLIENTIP); // the ip from the connecting peer
0818:
0819: int port, pos;
0820: if ((pos = host.indexOf(":")) < 0) {
0821: port = 80;
0822: } else {
0823: port = Integer.parseInt(host.substring(pos + 1));
0824: host = host.substring(0, pos);
0825: }
0826:
0827: yacyURL url = new yacyURL("http", host, port,
0828: (args == null) ? path : path + "?" + args);
0829: return url;
0830: }
0831:
0832: public static void handleTransparentProxySupport(httpHeader header,
0833: Properties prop, String virtualHost,
0834: boolean isTransparentProxy) {
0835: // transparent proxy support is only available for http 1.0 and above connections
0836: if (prop.getProperty(CONNECTION_PROP_HTTP_VER, "HTTP/0.9")
0837: .equals("HTTP/0.9"))
0838: return;
0839:
0840: // if the transparent proxy support was disabled, we have nothing todo here ...
0841: if (!(isTransparentProxy && header.containsKey(HOST)))
0842: return;
0843:
0844: // we only need to do the transparent proxy support if the request URL didn't contain the hostname
0845: // and therefor was set to virtualHost by function parseQuery()
0846: if (!prop.getProperty(CONNECTION_PROP_HOST).equals(virtualHost))
0847: return;
0848:
0849: // TODO: we could have problems with connections from extern here ...
0850: String dstHostSocket = (String) header.get(httpHeader.HOST);
0851: prop.setProperty(CONNECTION_PROP_HOST, (httpd
0852: .isThisHostName(dstHostSocket) ? virtualHost
0853: : dstHostSocket));
0854: }
0855:
0856: /*
0857: * Patch BEGIN:
0858: * Name: Header Property Patch
0859: * Date: Fri. 13.01.2006
0860: * Description: Makes possible to send header properties such as cookies back to the client.
0861: * Part 1 of 5
0862: * Questions: sergej.z@list.ru
0863: */
0864: /**
0865: * Holds header properties
0866: */
0867: //Since properties such as cookies can be multiple, we cannot use HashMap here. We have to use Vector.
0868: private Vector<Entry> cookies = new Vector<Entry>();
0869:
0870: /**
0871: * Implementation of Map.Entry. Structure that hold two values - exactly what we need!
0872: */
0873: class Entry implements Map.Entry<String, Object> {
0874: private String k;
0875: private Object v;
0876:
0877: Entry(String k, Object v) {
0878: this .k = k;
0879: this .v = v;
0880: }
0881:
0882: public String getKey() {
0883: return k;
0884: }
0885:
0886: public Object getValue() {
0887: return v;
0888: }
0889:
0890: public Object setValue(Object v) {
0891: Object r = this .v;
0892: this .v = v;
0893: return r;
0894: }
0895: }
0896:
0897: /**
0898: * Sets Cookie on the client machine.
0899: *
0900: * @param name Cookie name
0901: * @param value Cookie value
0902: * @param expires when should this cookie be autmatically deleted. If <b>null</b> - cookie will stay forever
0903: * @param path Path the cookie belongs to. Default - "/". Can be <b>null</b>.
0904: * @param domain Domain this cookie belongs to. Default - domain name. Can be <b>null</b>.
0905: * @param secure If true cookie will be send only over safe connection such as https
0906: * @see further documentation: <a href="http://docs.sun.com/source/816-6408-10/cookies.htm">docs.sun.com</a>
0907: */
0908: public void setCookie(String name, String value, String expires,
0909: String path, String domain, boolean secure) {
0910: /*
0911: * TODO:Here every value can be validated for correctness if needed
0912: * For example semicolon should be not in any of the values
0913: * However an exception in this case would be an overhead IMHO.
0914: */
0915: String cookieString = name + "=" + value + ";";
0916: if (expires != null)
0917: cookieString += " expires=" + expires + ";";
0918: if (path != null)
0919: cookieString += " path=" + path + ";";
0920: if (domain != null)
0921: cookieString += " domain=" + domain + ";";
0922: if (secure)
0923: cookieString += " secure;";
0924: cookies.add(new Entry("Set-Cookie", cookieString));
0925: }
0926:
0927: /**
0928: * Sets Cookie on the client machine.
0929: *
0930: * @param name Cookie name
0931: * @param value Cookie value
0932: * @param expires when should this cookie be autmatically deleted. If <b>null</b> - cookie will stay forever
0933: * @param path Path the cookie belongs to. Default - "/". Can be <b>null</b>.
0934: * @param domain Domain this cookie belongs to. Default - domain name. Can be <b>null</b>.
0935: *
0936: * Note: this cookie will be sent over each connection independend if it is safe connection or not.
0937: * @see further documentation: <a href="http://docs.sun.com/source/816-6408-10/cookies.htm">docs.sun.com</a>
0938: */
0939: public void setCookie(String name, String value, String expires,
0940: String path, String domain) {
0941: setCookie(name, value, expires, path, domain, false);
0942: }
0943:
0944: /**
0945: * Sets Cookie on the client machine.
0946: *
0947: * @param name Cookie name
0948: * @param value Cookie value
0949: * @param expires when should this cookie be autmatically deleted. If <b>null</b> - cookie will stay forever
0950: * @param path Path the cookie belongs to. Default - "/". Can be <b>null</b>.
0951: *
0952: * Note: this cookie will be sent over each connection independend if it is safe connection or not.
0953: * @see further documentation: <a href="http://docs.sun.com/source/816-6408-10/cookies.htm">docs.sun.com</a>
0954: */
0955: public void setCookie(String name, String value, String expires,
0956: String path) {
0957: setCookie(name, value, expires, path, null, false);
0958: }
0959:
0960: /**
0961: * Sets Cookie on the client machine.
0962: *
0963: * @param name Cookie name
0964: * @param value Cookie value
0965: * @param expires when should this cookie be autmatically deleted. If <b>null</b> - cookie will stay forever
0966: *
0967: * Note: this cookie will be sent over each connection independend if it is safe connection or not.
0968: * @see further documentation: <a href="http://docs.sun.com/source/816-6408-10/cookies.htm">docs.sun.com</a>
0969: */
0970: public void setCookie(String name, String value, String expires) {
0971: setCookie(name, value, expires, null, null, false);
0972: }
0973:
0974: /**
0975: * Sets Cookie on the client machine.
0976: *
0977: * @param name Cookie name
0978: * @param value Cookie value
0979: *
0980: * Note: this cookie will be sent over each connection independend if it is safe connection or not. This cookie never expires
0981: * @see further documentation: <a href="http://docs.sun.com/source/816-6408-10/cookies.htm">docs.sun.com</a>
0982: */
0983: public void setCookie(String name, String value) {
0984: setCookie(name, value, null, null, null, false);
0985: }
0986:
0987: public String getHeaderCookies() {
0988: Iterator<Map.Entry<String, String>> it = this .entrySet()
0989: .iterator();
0990: while (it.hasNext()) {
0991: Map.Entry<String, String> e = it.next();
0992: //System.out.println(""+e.getKey()+" : "+e.getValue());
0993: if (e.getKey().equals("Cookie")) {
0994: return e.getValue().toString();
0995: }
0996: }
0997: return "";
0998: }
0999:
1000: public Vector<Entry> getCookieVector() {
1001: return cookies;
1002: }
1003:
1004: public void setCookieVector(Vector<Entry> mycookies) {
1005: cookies = mycookies;
1006: }
1007:
1008: /**
1009: * Returns an iterator within all properties can be reached.
1010: * Is used mainly by httpd.
1011: *
1012: * <p>Example:</p>
1013: * <pre>
1014: * Iterator it=serverObjects.getRequestProperties();
1015: * while(it.hasNext())
1016: * {
1017: * java.util.Map.Entry e=(java.util.Map.Entry)it.next();
1018: * String propertyName=e.getKey();
1019: * String propertyValue=e.getValue();
1020: * }</pre>
1021: * @return iterator to read all request properties.
1022: */
1023: public Iterator<Entry> getCookies() {
1024: return cookies.iterator();
1025: }
1026: /*
1027: * Patch END:
1028: * Name: Header Property Patch
1029: */
1030: }
|