package com.limegroup.gnutella.http;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.net.URLEncoder;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.UDPService;
import com.limegroup.gnutella.settings.ChatSettings;
import com.limegroup.gnutella.statistics.BandwidthStat;
import com.limegroup.gnutella.util.StringUtils;
/**
* This class supplies general facilities for handling HTTP, such as
* writing headers, extracting header values, etc..
*/
public final class HTTPUtils {
/**
* Constant for the carriage-return linefeed sequence that marks
* the end of an HTTP header
*/
private static final String CRLF = "\r\n";
/**
* Cached colon followed by a space to avoid excessive allocations.
*/
private static final String COLON_SPACE = ": ";
/**
* Cached colon to avoid excessive allocations.
*/
private static final String COLON = ":";
/**
* Cached slash to avoid excessive allocations.
*/
private static final String SLASH = "/";
/**
* Private constructor to ensure that this class cannot be constructed
*/
private HTTPUtils() {}
/**
* Writes an single http header to the specified
* <tt>OutputStream</tt> instance, with the specified header name
* and the specified header value.
*
* @param name the <tt>HTTPHeaderName</tt> instance containing the
* header name to write to the stream
* @param value the <tt>String</tt> instance containing the
* header value to write to the stream
* @param os the <tt>OutputStream</tt> instance to write to
*/
public static void writeHeader(HTTPHeaderName name, String value,
OutputStream os)
throws IOException {
if(name == null) {
throw new NullPointerException("null name in writing http header");
} else if(value == null) {
throw new NullPointerException("null value in writing http header: "+
name);
} else if(os == null) {
throw new NullPointerException("null os in writing http header: "+
name);
}
String header = createHeader(name, value);
os.write(header.getBytes());
BandwidthStat.HTTP_HEADER_UPSTREAM_BANDWIDTH.addData(header.length());
}
/**
* Writes an single http header to the specified
* <tt>OutputStream</tt> instance, with the specified header name
* and the specified header value.
*
* @param name the <tt>HTTPHeaderName</tt> instance containing the
* header name to write to the stream
* @param value the <tt>HTTPHeaderValue</tt> instance containing the
* header value to write to the stream
* @param out the <tt>Writer</tt> instance to write to
*/
public static void writeHeader(HTTPHeaderName name, String value, Writer out)
throws IOException {
if(name == null) {
throw new NullPointerException("null name in writing http header");
} else if(value == null) {
throw new NullPointerException("null value in writing http header: "+
name);
} else if(out == null) {
throw new NullPointerException("null os in writing http header: "+
name);
}
String header = createHeader(name, value);
out.write(header);
BandwidthStat.HTTP_HEADER_UPSTREAM_BANDWIDTH.addData(header.length());
}
/**
* Writes an single http header to the specified
* <tt>OutputStream</tt> instance, with the specified header name
* and the specified header value.
*
* @param name the <tt>HTTPHeaderName</tt> instance containing the
* header name to write to the stream
* @param name the <tt>HTTPHeaderValue</tt> instance containing the
* header value to write to the stream
* @param os the <tt>OutputStream</tt> instance to write to
*/
public static void writeHeader(HTTPHeaderName name, HTTPHeaderValue value, OutputStream os)
throws IOException {
if(name == null) {
throw new NullPointerException("null name in writing http header");
} else if(value == null) {
throw new NullPointerException("null value in writing http header: "+
name);
} else if(os == null) {
throw new NullPointerException("null os in writing http header: "+
name);
}
String header = createHeader(name, value.httpStringValue());
os.write(header.getBytes());
BandwidthStat.HTTP_HEADER_UPSTREAM_BANDWIDTH.addData(header.length());
}
/**
* Writes an single http header to the specified
* <tt>OutputStream</tt> instance, with the specified header name
* and the specified header value.
*
* @param name the <tt>HTTPHeaderName</tt> instance containing the
* header name to write to the stream
* @param name the <tt>HTTPHeaderValue</tt> instance containing the
* header value to write to the stream
* @param out the <tt>Writer</tt> instance to write to
*/
public static void writeHeader(HTTPHeaderName name, HTTPHeaderValue value,
Writer out)
throws IOException {
if(name == null) {
throw new NullPointerException("null name in writing http header");
} else if(value == null) {
throw new NullPointerException("null value in writing http header: "+
name);
} else if(out == null) {
throw new NullPointerException("null os in writing http header: "+
name);
}
String header = createHeader(name, value.httpStringValue());
out.write(header);
BandwidthStat.HTTP_HEADER_UPSTREAM_BANDWIDTH.addData(header.length());
}
/**
* Create a single http header String with the specified header name
* and the specified header value.
*
* @param name the <tt>HTTPHeaderName</tt> instance containing the
* header name
* @param valueStr the value of the header, generally the httpStringValue
* or a HttpHeaderValue, or just a String.
*/
private static String createHeader(HTTPHeaderName name, String valueStr)
throws IOException {
if((name == null) || (valueStr == null)) {
throw new NullPointerException("null value in creating http header");
}
String nameStr = name.httpStringValue();
if(nameStr == null) {
throw new NullPointerException("null value in creating http header");
}
StringBuffer sb = new StringBuffer();
sb.append(nameStr);
sb.append(COLON_SPACE);
sb.append(valueStr);
sb.append(CRLF);
return sb.toString();
}
/**
* Parses out the header value from the HTTP header string.
*
* @return the header value for the specified full header string, or
* <tt>null</tt> if the value could not be extracted
*/
public static String extractHeaderValue(final String header) {
int index = header.indexOf(COLON);
if(index <= 0) return null;
return header.substring(index+1).trim();
}
/**
* Utility method for writing a header with an integer value. This removes
* the burden to the caller of converting integer HTTP values to strings.
*
* @param name the <tt>HTTPHeaderName</tt> of the header to write
* @param value the int value of the header
* @param writer the <tt>Writer</tt> instance to write the header to
* @throws IOException if an IO error occurs during the write
*/
public static void writeHeader(HTTPHeaderName name, int value, Writer writer) throws IOException {
writeHeader(name, String.valueOf(value), writer);
}
/**
* Utility method for writing a header with an integer value. This removes
* the burden to the caller of converting integer HTTP values to strings.
*
* @param name the <tt>HTTPHeaderName</tt> of the header to write
* @param value the int value of the header
* @param stream the <tt>OutputStream</tt> instance to write the header to
* @throws IOException if an IO error occurs during the write
*/
public static void writeHeader(HTTPHeaderName name, int value, OutputStream stream) throws IOException {
writeHeader(name, String.valueOf(value), stream);
}
/**
* Writes the Content-Disposition header, assuming an 'attachment'.
*/
public static void writeContentDisposition(String name, Writer writer) throws IOException {
writeHeader(HTTPHeaderName.CONTENT_DISPOSITION,
"attachment; filename=\"" + encode(name, "US-ASCII") + "\"",
writer);
}
/**
* Utility method for writing the "Date" header, as specified in RFC 2616
* section 14.18, to a <tt>Writer</tt>.
*
* @param writer the <tt>Writer</tt> to write the header to
* @throws IOException if a write error occurs
*/
public static void writeDate(Writer writer) throws IOException {
writeHeader(HTTPHeaderName.DATE, getDateValue(), writer);
}
/**
* Utility method for writing the "Date" header, as specified in RFC 2616
* section 14.18, to a <tt>OutputStream</tt>.
*
* @param stream the <tt>OutputStream</tt> to write the header to
* @throws IOException if a write error occurs
*/
public static void writeDate(OutputStream stream) throws IOException {
writeHeader(HTTPHeaderName.DATE, getDateValue(), stream);
}
/**
* Utility method for writing the currently supported features
* to the <tt>Writer</tt>.
*/
public static void writeFeatures(Writer writer) throws IOException {
Set features = getFeaturesValue();
// Write X-Features header.
if (features.size() > 0) {
writeHeader(HTTPHeaderName.FEATURES,
new HTTPHeaderValueCollection(features), writer);
}
}
/**
* Utility method for writing the currently supported features
* to the <tt>OutputStream</tt>.
*/
public static void writeFeatures(OutputStream stream) throws IOException {
Set features = getFeaturesValue();
// Write X-Features header.
if (features.size() > 0) {
writeHeader(HTTPHeaderName.FEATURES,
new HTTPHeaderValueCollection(features), stream);
}
}
/**
* Utlity method for getting the currently supported features.
*/
private static Set getFeaturesValue() {
Set features = new HashSet(4);
features.add(ConstantHTTPHeaderValue.BROWSE_FEATURE);
if (ChatSettings.CHAT_ENABLED.getValue())
features.add(ConstantHTTPHeaderValue.CHAT_FEATURE);
features.add(ConstantHTTPHeaderValue.PUSH_LOCS_FEATURE);
if (!RouterService.acceptedIncomingConnection() && UDPService.instance().canDoFWT())
features.add(ConstantHTTPHeaderValue.FWT_PUSH_LOCS_FEATURE);
return features;
}
/**
* Utility method for extracting the version from a feature token.
*/
public static float parseFeatureToken(String token) throws
ProblemReadingHeaderException{
int slashIndex = token.indexOf(SLASH);
if (slashIndex == -1 || slashIndex >= token.length()-1)
throw new ProblemReadingHeaderException("invalid feature token");
String versionS = token.substring(slashIndex+1);
try {
return Float.parseFloat(versionS);
}catch (NumberFormatException bad) {
throw new ProblemReadingHeaderException(bad);
}
}
/**
* Utility method for getting the date value for the "Date" header in
* standard format.
*
* @return the current date as a standardized date string -- see
* RFC 2616 section 14.18
*/
private static String getDateValue() {
DateFormat df =
new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", Locale.US);
df.setTimeZone(TimeZone.getTimeZone("GMT"));
return df.format(new Date());
}
/**
* Encodes a name using URLEncoder, using %20 instead of + for spaces.
*/
private static String encode(String name, String encoding) throws IOException {
return StringUtils.replace(URLEncoder.encode(name, encoding), "+", "%20");
}
}