/**
* Copyright 2008 - CommonCrawl Foundation
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
**/
package org.commoncrawl.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.util.StringUtils;
import org.apache.tools.ant.filters.StringInputStream;
/*
* Derived FROM HttpHeaders in OpenJDK
*
*/
/*-
* news stream opener
*/
public final class NIOHttpHeaders {
public class HeaderIterator implements Iterator<String> {
int index = 0;
int next = -1;
String key;
boolean haveNext = false;
Object lock;
public HeaderIterator(String k, Object lock) {
key = k;
this.lock = lock;
}
public boolean hasNext() {
synchronized (lock) {
if (haveNext) {
return true;
}
while (index < nkeys) {
if (key.equalsIgnoreCase(keys[index])) {
haveNext = true;
next = index++;
return true;
}
index++;
}
return false;
}
}
public String next() {
synchronized (lock) {
if (haveNext) {
haveNext = false;
return values[next];
}
if (hasNext()) {
return next();
} else {
throw new NoSuchElementException("No more elements");
}
}
}
public void remove() {
throw new UnsupportedOperationException("remove not allowed");
}
}
/** logging **/
private static final Log LOG = LogFactory.getLog(NIOHttpHeaders.class);
/**
* Convert a message-id string to canonical form (strips off leading and
* trailing <>s)
*/
public static String canonicalID(String id) {
if (id == null)
return "";
int st = 0;
int len = id.length();
boolean substr = false;
int c;
while (st < len && ((c = id.charAt(st)) == '<' || c <= ' ')) {
st++;
substr = true;
}
while (st < len && ((c = id.charAt(len - 1)) == '>' || c <= ' ')) {
len--;
substr = true;
}
return substr ? id.substring(st, len) : id;
}
public static NIOHttpHeaders parseHttpHeaders(String headers) throws IOException {
NIOHttpHeaders headersOut = new NIOHttpHeaders();
if (headers != null && headers.length() != 0) {
headersOut.parseHeader(new StringInputStream(headers));
/*
*/
}
return headersOut;
}
public static NIOHttpHeaders parseHttpHeadersAlternate(String headers) {
NIOHttpHeaders headersOut = new NIOHttpHeaders();
if (headers != null && headers.length() != 0) {
StringTokenizer tokenizer = new StringTokenizer(headers, "\r\n");
while (tokenizer.hasMoreElements()) {
String token = tokenizer.nextToken();
if (token != null && token.length() != 0) {
int colonPos = token.indexOf(':');
if (colonPos != -1 && colonPos != token.length() - 1) {
String key = token.substring(0, colonPos);
String value = token.substring(colonPos + 1);
if (key.length() != 0 && value.length() != 0) {
headersOut.add(key, value);
}
} else {
headersOut.add(null, token);
}
}
}
}
return headersOut;
}
/**
* An RFC 844 or MIME message header. Includes methods for parsing headers
* from incoming streams, fetching values, setting values, and printing
* headers. Key values of null are legal: they indicate lines in the header
* that don't have a valid key, but do have a value (this isn't legal
* according to the standard, but lines like this are everywhere).
*/
private String keys[];
private String values[];
private int nkeys;
public NIOHttpHeaders() {
grow();
}
public NIOHttpHeaders(InputStream is) throws java.io.IOException {
parseHeader(is);
}
/**
* Adds a key value pair to the end of the header. Duplicates are allowed
*/
public synchronized void add(String k, String v) {
grow();
keys[nkeys] = k;
values[nkeys] = v;
nkeys++;
}
/**
* Deprecated: Use multiValueIterator() instead.
*
* Find the next value that corresponds to this key. It finds the first value
* that follows v. To iterate over all the values of a key use:
*
* <pre>
* for(String v=h.findValue(k); v!=null; v=h.findNextValue(k, v)) {
* ...
* }
* </pre>
*/
public synchronized String findNextValue(String k, String v) {
boolean foundV = false;
if (k == null) {
for (int i = nkeys; --i >= 0;)
if (keys[i] == null)
if (foundV)
return values[i];
else if (values[i] == v)
foundV = true;
} else
for (int i = nkeys; --i >= 0;)
if (k.equalsIgnoreCase(keys[i]))
if (foundV)
return values[i];
else if (values[i] == v)
foundV = true;
return null;
}
/**
* Find the value that corresponds to this key. It finds only the first
* occurrence of the key.
*
* @param k
* the key to find.
* @return null if not found.
*/
public synchronized String findValue(String k) {
if (k == null) {
for (int i = nkeys; --i >= 0;)
if (keys[i] == null)
return values[i];
} else
for (int i = nkeys; --i >= 0;) {
if (k.equalsIgnoreCase(keys[i]))
return values[i];
}
return null;
}
public synchronized Map getHeaders() {
return getHeaders(null);
}
public synchronized Map<String, List<String>> getHeaders(String[] excludeList) {
boolean skipIt = false;
Map<String, List<String>> m = new HashMap<String, List<String>>();
for (int i = nkeys; --i >= 0;) {
if (excludeList != null) {
// check if the key is in the excludeList.
// if so, don't include it in the Map.
for (int j = 0; j < excludeList.length; j++) {
if ((excludeList[j] != null) && (excludeList[j].equalsIgnoreCase(keys[i]))) {
skipIt = true;
break;
}
}
}
if (!skipIt) {
List<String> l = m.get(keys[i]);
if (l == null) {
l = new ArrayList<String>();
m.put(keys[i], l);
}
l.add(values[i]);
} else {
// reset the flag
skipIt = false;
}
}
Set<String> keySet = m.keySet();
for (Iterator<String> i = keySet.iterator(); i.hasNext();) {
String key = i.next();
List<String> l = m.get(key);
m.put(key, Collections.unmodifiableList(l));
}
return Collections.unmodifiableMap(m);
}
/** extract http result code from headers **/
public int getHttpResponseCode() {
int responseCode = -1;
try {
String responseLine = getValue(0);
if (responseLine != null) {
int index;
index = responseLine.indexOf(' ');
while (index < responseLine.length() && responseLine.charAt(index) == ' ')
index++;
if (index + 2 < responseLine.length()) {
responseCode = Integer.parseInt(responseLine.substring(index, index + 3));
}
}
} catch (Exception e) {
LOG.error(StringUtils.stringifyException(e));
}
return responseCode;
}
public synchronized String getKey(int n) {
if (n < 0 || n >= nkeys)
return null;
return keys[n];
}
// return the location of the key
public synchronized int getKey(String k) {
for (int i = nkeys; --i >= 0;)
if ((keys[i] == k) || (k != null && k.equalsIgnoreCase(keys[i])))
return i;
return -1;
}
public synchronized int getKeyCount() {
return nkeys;
}
public synchronized String getValue(int n) {
if (n < 0 || n >= nkeys)
return null;
return values[n];
}
/** grow the key/value arrays as needed */
private void grow() {
if (keys == null || nkeys >= keys.length) {
String[] nk = new String[nkeys + 4];
String[] nv = new String[nkeys + 4];
if (keys != null)
System.arraycopy(keys, 0, nk, 0, nkeys);
if (values != null)
System.arraycopy(values, 0, nv, 0, nkeys);
keys = nk;
values = nv;
}
}
/** Parse and merge a MIME header from an input stream. */
public void mergeHeader(InputStream is) throws java.io.IOException {
if (is == null)
return;
char s[] = new char[10];
int firstc = is.read();
while (firstc != '\n' && firstc != '\r' && firstc >= 0) {
int len = 0;
int keyend = -1;
int c;
boolean inKey = firstc > ' ';
s[len++] = (char) firstc;
parseloop: {
while ((c = is.read()) > 0) {
switch (c) {
case ':':
if (inKey && len > 0)
keyend = len;
inKey = false;
break;
case '\t':
c = ' ';
case ' ':
inKey = false;
break;
case '\r':
case '\n':
firstc = is.read();
if (c == '\r' && firstc == '\n') {
firstc = is.read();
if (firstc == '\r')
firstc = is.read();
}
if (firstc == '\n' || firstc == '\r' || firstc > ' ')
break parseloop;
/* continuation */
c = ' ';
break;
}
if (len >= s.length) {
char ns[] = new char[s.length * 2];
System.arraycopy(s, 0, ns, 0, len);
s = ns;
}
s[len++] = (char) c;
}
firstc = -1;
}
while (len > 0 && s[len - 1] <= ' ')
len--;
String k;
if (keyend <= 0) {
k = null;
keyend = 0;
} else {
k = String.copyValueOf(s, 0, keyend);
if (keyend < len && s[keyend] == ':')
keyend++;
while (keyend < len && s[keyend] <= ' ')
keyend++;
}
String v;
if (keyend >= len)
v = new String();
else
v = String.copyValueOf(s, keyend, len - keyend);
add(k, v.trim());
}
}
/**
* return an Iterator that returns all values of a particular key in sequence
*/
public Iterator<String> multiValueIterator(String k) {
return new HeaderIterator(k, this);
}
/** Parse a MIME header from an input stream. */
public void parseHeader(InputStream is) throws java.io.IOException {
synchronized (this) {
nkeys = 0;
}
mergeHeader(is);
}
/**
* Prepends a key value pair to the beginning of the header. Duplicates are
* allowed
*/
public synchronized void prepend(String k, String v) {
grow();
for (int i = nkeys; i > 0; i--) {
keys[i] = keys[i - 1];
values[i] = values[i - 1];
}
keys[0] = k;
values[0] = v;
nkeys++;
}
/**
* Prints the key-value pairs represented by this header. Also prints the RFC
* required blank line at the end. Omits pairs with a null key.
*/
public synchronized void print(PrintWriter p) {
for (int i = 0; i < nkeys; i++)
if (keys[i] != null) {
p.print(keys[i] + (values[i] != null ? ": " + values[i] : "") + "\r\n");
// System.out.print(keys[i] +(values[i] != null ? ": "+values[i]: "") +
// "\r\n");
}
p.print("\r\n");
// System.out.print("\r\n");
p.flush();
}
/**
* Remove the key from the header. If there are multiple values under the same
* key, they are all removed. Nothing is done if the key doesn't exist. After
* a remove, the other pairs' order are not changed.
*
* @param k
* the key to remove
*/
public synchronized void remove(String k) {
if (k == null) {
for (int i = 0; i < nkeys; i++) {
while (keys[i] == null && i < nkeys) {
for (int j = i; j < nkeys - 1; j++) {
keys[j] = keys[j + 1];
values[j] = values[j + 1];
}
nkeys--;
}
}
} else {
for (int i = 0; i < nkeys; i++) {
while (k.equalsIgnoreCase(keys[i]) && i < nkeys) {
for (int j = i; j < nkeys - 1; j++) {
keys[j] = keys[j + 1];
values[j] = values[j + 1];
}
nkeys--;
}
}
}
}
/**
* Reset a message header (all key/values removed)
*/
public synchronized void reset() {
keys = null;
values = null;
nkeys = 0;
grow();
}
/**
* Overwrite the previous key/val pair at location 'i' with the new k/v. If
* the index didn't exist before the key/val is simply tacked onto the end.
*/
public synchronized void set(int i, String k, String v) {
grow();
if (i < 0) {
return;
} else if (i >= nkeys) {
add(k, v);
} else {
keys[i] = k;
values[i] = v;
}
}
/**
* Sets the value of a key. If the key already exists in the header, it's
* value will be changed. Otherwise a new key/value pair will be added to the
* end of the header.
*/
public synchronized void set(String k, String v) {
for (int i = nkeys; --i >= 0;)
if (k.equalsIgnoreCase(keys[i])) {
values[i] = v;
return;
}
add(k, v);
}
/**
* Set's the value of a key only if there is no key with that value already.
*/
public synchronized void setIfNotSet(String k, String v) {
if (findValue(k) == null) {
add(k, v);
}
}
@Override
public synchronized String toString() {
StringWriter writer = new StringWriter();
for (int i = 0; i < keys.length && i < nkeys; i++) {
if (keys[i] != null && keys[i].length() != 0) {
writer.write(keys[i]);
writer.write(":");
}
writer.write(values[i]);
writer.write("\r\n");
}
return writer.toString();
}
}