/****************************************************************** * * CyberHTTP for Java * * Copyright (C) Satoshi Konno 2002-2004 * * File: HTTPConnection.java * * Revision; * * 11/18/02 - first revision. 09/02/03 - Giordano Sassaroli * <sassarol@cefriel.it> - Problem : The API is unable to receive responses from * the Microsoft UPnP stack - Error : the Microsoft UPnP stack is based on ISAPI * on IIS, and whenever IIS receives a post request, it answers with two * responses: the first one has no body and it is a code 100 (continue) * response, which has to be ignored. The second response is the actual one and * should be parsed as the response. 02/09/04 - Ralf G. R. Bergs" <Ralf@Ber.gs> * - Why do you strip leading and trailing white space from the response body? - * Disabled to trim the content string. 03/11/04 - Added some methods about * InputStream content. setContentInputStream(), getContentInputStream() and * hasContentInputStream(). 03/16/04 - Thanks for Darrell Young - Added * setVersion() and getVersion(); 03/17/04 - Added hasFirstLine(); 05/26/04 - * Jan Newmarch <jan.newmarch@infotech.monash.edu.au> (05/26/04) - Changed * setCacheControl() and getChcheControl(); 08/25/04 - Added the following * methods. hasContentRange(), setContentRange(), getContentRange(), * getContentRangeFirstPosition(), getContentRangeLastPosition() and * getContentRangeInstanceLength() 08/26/04 - Added the following methods. * hasConnection(), setConnection(), getConnection(), isCloseConnection() and * isKeepAliveConnection() 08/27/04 - Added a updateWithContentLength paramger * to setContent(). - Changed to HTTPPacket::set() not to change the header of * Content-Length. 08/28/04 - Added init() and read(). 09/19/04 - Added a * onlyHeaders parameter to set(). 10/20/04 - Brent Hills * <bhills@openshores.com> - Changed hasContentRange() to check Content-Range * and Range header. - Added support for Range header to getContentRange(). * 02/02/05 - Mark Retallack <mretallack@users.sourceforge.net> - Fixed set() * not to read over the content length when the stream is keep alive. 02/28/05 - * Added the following methods for chunked stream support. * hasTransferEncoding(), setTransferEncoding(), getTransferEncoding(), * isChunked(). 03/02/05 - Changed post() to suppot chunked stream. 06/11/05 - * Added setHost(). 07/07/05 - Lee Peik Feng <pflee@users.sourceforge.net> - * Andrey Ovchar <AOvchar@consultitnow.com> - Fixed set() to parse the chunk * size as a hex string. 11/02/05 - Changed set() to use BufferedInputStream * instead of BufferedReader to get the content as a byte stream. 11/06/05 - * Added getCharSet(). - Changed getContentString() to return the content string * using the charset. * *******************************************************************/ package com.guo.duoduo.airplayreceiver.http; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.util.Calendar; import java.util.StringTokenizer; import java.util.Vector; import com.guo.duoduo.airplayreceiver.utils.Debug; import com.guo.duoduo.airplayreceiver.utils.HostInterface; import com.guo.duoduo.airplayreceiver.utils.StringUtil; public class HTTPPacket { //////////////////////////////////////////////// // Constructor //////////////////////////////////////////////// private String version; private String firstLine = ""; private Vector httpHeaderList = new Vector(); //////////////////////////////////////////////// // init //////////////////////////////////////////////// private byte content[] = new byte[0]; //////////////////////////////////////////////// // Version //////////////////////////////////////////////// private InputStream contentInput = null; public HTTPPacket() { setVersion(HTTP.VERSION); setContentInputStream(null); } public HTTPPacket(HTTPPacket httpPacket) { setVersion(HTTP.VERSION); set(httpPacket); setContentInputStream(null); } //////////////////////////////////////////////// // set //////////////////////////////////////////////// public HTTPPacket(InputStream in) { setVersion(HTTP.VERSION); set(in); setContentInputStream(null); } public void init() { setFirstLine(""); clearHeaders(); setContent(new byte[0], false); setContentInputStream(null); } public String getVersion() { return version; } public void setVersion(String ver) { version = ver; } private String readLine(BufferedInputStream in) { ByteArrayOutputStream lineBuf = new ByteArrayOutputStream(); byte readBuf[] = new byte[1]; try { int readLen = in.read(readBuf); while (0 < readLen) { if (readBuf[0] == HTTP.LF) break; if (readBuf[0] != HTTP.CR) lineBuf.write(readBuf[0]); readLen = in.read(readBuf); } } catch (InterruptedIOException e) { //Ignoring warning because it's a way to break the HTTP connecttion //TODO Create a new level of Logging and log the event } catch (IOException e) { Debug.warning(e); } return lineBuf.toString(); } //////////////////////////////////////////////// // read //////////////////////////////////////////////// protected boolean set(InputStream in, boolean onlyHeaders) { try { BufferedInputStream reader = new BufferedInputStream(in); String firstLine = readLine(reader); if (firstLine == null || firstLine.length() <= 0) return false; setFirstLine(firstLine); // Thanks for Giordano Sassaroli <sassarol@cefriel.it> (09/03/03) HTTPStatus httpStatus = new HTTPStatus(firstLine); int statCode = httpStatus.getStatusCode(); if (statCode == HTTPStatus.CONTINUE) { //ad hoc code for managing iis non-standard behaviour //iis sends 100 code response and a 200 code response in the same //stream, so the code should check the presence of the actual //response in the stream. //skip all header lines String headerLine = readLine(reader); while ((headerLine != null) && (0 < headerLine.length())) { HTTPHeader header = new HTTPHeader(headerLine); if (header.hasName() == true) setHeader(header); headerLine = readLine(reader); } //look forward another first line String actualFirstLine = readLine(reader); if ((actualFirstLine != null) && (0 < actualFirstLine.length())) { //this is the actual first line setFirstLine(actualFirstLine); } else { return true; } } String headerLine = readLine(reader); while ((headerLine != null) && (0 < headerLine.length())) { HTTPHeader header = new HTTPHeader(headerLine); if (header.hasName() == true) setHeader(header); headerLine = readLine(reader); } if (onlyHeaders == true) { setContent("", false); return true; } boolean isChunkedRequest = isChunked(); long contentLen = 0; if (isChunkedRequest == true) { try { String chunkSizeLine = readLine(reader); // Thanks for Lee Peik Feng <pflee@users.sourceforge.net> (07/07/05) //contentLen = Long.parseLong(new String(chunkSizeLine.getBytes(), 0, chunkSizeLine.length()-2), 16); contentLen = (chunkSizeLine != null) ? Long.parseLong( chunkSizeLine.trim(), 16) : 0; } catch (Exception e) { }; } else contentLen = getContentLength(); ByteArrayOutputStream contentBuf = new ByteArrayOutputStream(); while (0 < contentLen) { int chunkSize = HTTP.getChunkSize(); /* Thanks for Stephan Mehlhase (2010-10-26) */ byte readBuf[] = new byte[(int) (contentLen > chunkSize ? chunkSize : contentLen)]; long readCnt = 0; while (readCnt < contentLen) { try { // Thanks for Mark Retallack (02/02/05) long bufReadLen = contentLen - readCnt; if (chunkSize < bufReadLen) bufReadLen = chunkSize; int readLen = reader.read(readBuf, 0, (int) bufReadLen); if (readLen < 0) break; contentBuf.write(readBuf, 0, readLen); readCnt += readLen; } catch (Exception e) { Debug.warning(e); break; } } if (isChunkedRequest == true) { // skip CRLF long skipLen = 0; do { long skipCnt = reader.skip(HTTP.CRLF.length() - skipLen); if (skipCnt < 0) break; skipLen += skipCnt; } while (skipLen < HTTP.CRLF.length()); // read next chunk size try { String chunkSizeLine = readLine(reader); // Thanks for Lee Peik Feng <pflee@users.sourceforge.net> (07/07/05) contentLen = Long.parseLong(new String(chunkSizeLine.getBytes(), 0, chunkSizeLine.length() - 2), 16); } catch (Exception e) { contentLen = 0; }; } else contentLen = 0; } setContent(contentBuf.toByteArray(), false); } catch (Exception e) { Debug.warning(e); return false; } return true; } //////////////////////////////////////////////// // String //////////////////////////////////////////////// protected boolean set(InputStream in) { return set(in, false); } protected boolean set(HTTPSocket httpSock) { return set(httpSock.getInputStream()); } protected void set(HTTPPacket httpPacket) { setFirstLine(httpPacket.getFirstLine()); clearHeaders(); int nHeaders = httpPacket.getNHeaders(); for (int n = 0; n < nHeaders; n++) { HTTPHeader header = httpPacket.getHeader(n); addHeader(header); } setContent(httpPacket.getContent()); } public boolean read(HTTPSocket httpSock) { init(); return set(httpSock); } protected String getFirstLine() { return firstLine; } //////////////////////////////////////////////// // Header //////////////////////////////////////////////// private void setFirstLine(String value) { firstLine = value; } protected String getFirstLineToken(int num) { StringTokenizer st = new StringTokenizer(firstLine, HTTP.REQEST_LINE_DELIM); String lastToken = ""; for (int n = 0; n <= num; n++) { if (st.hasMoreTokens() == false) return ""; lastToken = st.nextToken(); } return lastToken; } public boolean hasFirstLine() { return (0 < firstLine.length()) ? true : false; } public int getNHeaders() { return httpHeaderList.size(); } public void addHeader(HTTPHeader header) { httpHeaderList.add(header); } public void addHeader(String name, String value) { HTTPHeader header = new HTTPHeader(name, value); httpHeaderList.add(header); } public HTTPHeader getHeader(int n) { return (HTTPHeader) httpHeaderList.get(n); } public HTTPHeader getHeader(String name) { int nHeaders = getNHeaders(); for (int n = 0; n < nHeaders; n++) { HTTPHeader header = getHeader(n); String headerName = header.getName(); if (headerName.equalsIgnoreCase(name) == true) return header; } return null; } public void clearHeaders() { httpHeaderList.clear(); httpHeaderList = new Vector(); } public boolean hasHeader(String name) { return (getHeader(name) != null) ? true : false; } public void setHeader(String name, String value) { HTTPHeader header = getHeader(name); if (header != null) { header.setValue(value); return; } addHeader(name, value); } public void setHeader(String name, int value) { setHeader(name, Integer.toString(value)); } public void setHeader(String name, long value) { setHeader(name, Long.toString(value)); } //////////////////////////////////////////////// // set*Value //////////////////////////////////////////////// public void setHeader(HTTPHeader header) { setHeader(header.getName(), header.getValue()); } public String getHeaderValue(String name) { HTTPHeader header = getHeader(name); if (header == null) return ""; return header.getValue(); } public void setStringHeader(String name, String value, String startWidth, String endWidth) { String headerValue = value; if (headerValue.startsWith(startWidth) == false) headerValue = startWidth + headerValue; if (headerValue.endsWith(endWidth) == false) headerValue = headerValue + endWidth; setHeader(name, headerValue); } public void setStringHeader(String name, String value) { setStringHeader(name, value, "\"", "\""); } public String getStringHeaderValue(String name, String startWidth, String endWidth) { String headerValue = getHeaderValue(name); if (headerValue.startsWith(startWidth) == true) headerValue = headerValue.substring(1, headerValue.length()); if (headerValue.endsWith(endWidth) == true) headerValue = headerValue.substring(0, headerValue.length() - 1); return headerValue; } public String getStringHeaderValue(String name) { return getStringHeaderValue(name, "\"", "\""); } public void setIntegerHeader(String name, int value) { setHeader(name, Integer.toString(value)); } public void setLongHeader(String name, long value) { setHeader(name, Long.toString(value)); } //////////////////////////////////////////////// // getHeader //////////////////////////////////////////////// public int getIntegerHeaderValue(String name) { HTTPHeader header = getHeader(name); if (header == null) return 0; return StringUtil.toInteger(header.getValue()); } //////////////////////////////////////////////// // Contents //////////////////////////////////////////////// public long getLongHeaderValue(String name) { HTTPHeader header = getHeader(name); if (header == null) return 0; return StringUtil.toLong(header.getValue()); } public String getHeaderString() { StringBuffer str = new StringBuffer(); int nHeaders = getNHeaders(); for (int n = 0; n < nHeaders; n++) { HTTPHeader header = getHeader(n); str.append(header.getName() + ": " + header.getValue() + HTTP.CRLF); } return str.toString(); } public void setContent(byte data[], boolean updateWithContentLength) { content = data; if (updateWithContentLength == true) setContentLength(data.length); } public void setContent(byte data[]) { setContent(data, true); } public void setContent(String data, boolean updateWithContentLength) { setContent(data.getBytes(), updateWithContentLength); } public byte[] getContent() { return content; } public void setContent(String data) { setContent(data, true); } public String getContentString() { String charSet = getCharSet(); if (charSet == null || charSet.length() <= 0) return new String(content); try { return new String(content, charSet); } catch (Exception e) { Debug.warning(e); } return new String(content); } //////////////////////////////////////////////// // Contents (InputStream) //////////////////////////////////////////////// public boolean hasContent() { return (content.length > 0) ? true : false; } public InputStream getContentInputStream() { return contentInput; } public void setContentInputStream(InputStream in) { contentInput = in; } public boolean hasContentInputStream() { return (contentInput != null) ? true : false; } //////////////////////////////////////////////// // ContentType //////////////////////////////////////////////// public String getContentType() { return getHeaderValue(HTTP.CONTENT_TYPE); } public void setContentType(String type) { setHeader(HTTP.CONTENT_TYPE, type); } //////////////////////////////////////////////// // ContentLanguage //////////////////////////////////////////////// public String getContentLanguage() { return getHeaderValue(HTTP.CONTENT_LANGUAGE); } public void setContentLanguage(String code) { setHeader(HTTP.CONTENT_LANGUAGE, code); } //////////////////////////////////////////////// // Charset //////////////////////////////////////////////// public String getCharSet() { String contentType = getContentType(); if (contentType == null) return ""; contentType = contentType.toLowerCase(); int charSetIdx = contentType.indexOf(HTTP.CHARSET); if (charSetIdx < 0) return ""; int charSetEndIdx = charSetIdx + HTTP.CHARSET.length() + 1; String charSet = new String(contentType.getBytes(), charSetEndIdx, (contentType.length() - charSetEndIdx)); if (charSet.length() < 0) return ""; if (charSet.charAt(0) == '\"') charSet = charSet.substring(1, (charSet.length() - 1)); if (charSet.length() < 0) return ""; if (charSet.charAt((charSet.length() - 1)) == '\"') charSet = charSet.substring(0, (charSet.length() - 1)); return charSet; } //////////////////////////////////////////////// // ContentLength //////////////////////////////////////////////// public long getContentLength() { return getLongHeaderValue(HTTP.CONTENT_LENGTH); } public void setContentLength(long len) { setLongHeader(HTTP.CONTENT_LENGTH, len); } //////////////////////////////////////////////// // Connection //////////////////////////////////////////////// public boolean hasConnection() { return hasHeader(HTTP.CONNECTION); } public String getConnection() { return getHeaderValue(HTTP.CONNECTION); } public void setConnection(String value) { setHeader(HTTP.CONNECTION, value); } public boolean isCloseConnection() { if (hasConnection() == false) return false; String connection = getConnection(); if (connection == null) return false; return connection.equalsIgnoreCase(HTTP.CLOSE); } public boolean isKeepAliveConnection() { if (hasConnection() == false) return false; String connection = getConnection(); if (connection == null) return false; return connection.equalsIgnoreCase(HTTP.KEEP_ALIVE); } //////////////////////////////////////////////// // ContentRange //////////////////////////////////////////////// public boolean hasContentRange() { return (hasHeader(HTTP.CONTENT_RANGE) || hasHeader(HTTP.RANGE)); } public void setContentRange(long firstPos, long lastPos, long length) { String rangeStr = ""; rangeStr += HTTP.CONTENT_RANGE_BYTES + " "; rangeStr += Long.toString(firstPos) + "-"; rangeStr += Long.toString(lastPos) + "/"; rangeStr += ((0 < length) ? Long.toString(length) : "*"); setHeader(HTTP.CONTENT_RANGE, rangeStr); } public long[] getContentRange() { long range[] = new long[3]; range[0] = range[1] = range[2] = 0; if (hasContentRange() == false) return range; String rangeLine = getHeaderValue(HTTP.CONTENT_RANGE); // Thanks for Brent Hills (10/20/04) if (rangeLine.length() <= 0) rangeLine = getHeaderValue(HTTP.RANGE); if (rangeLine.length() <= 0) return range; // Thanks for Brent Hills (10/20/04) StringTokenizer strToken = new StringTokenizer(rangeLine, " ="); // Skip bytes if (strToken.hasMoreTokens() == false) return range; String bytesStr = strToken.nextToken(" "); // Get first-byte-pos if (strToken.hasMoreTokens() == false) return range; String firstPosStr = strToken.nextToken(" -"); try { range[0] = Long.parseLong(firstPosStr); } catch (NumberFormatException e) { }; if (strToken.hasMoreTokens() == false) return range; String lastPosStr = strToken.nextToken("-/"); try { range[1] = Long.parseLong(lastPosStr); } catch (NumberFormatException e) { }; if (strToken.hasMoreTokens() == false) return range; String lengthStr = strToken.nextToken("/"); try { range[2] = Long.parseLong(lengthStr); } catch (NumberFormatException e) { }; return range; } public long getContentRangeFirstPosition() { long range[] = getContentRange(); return range[0]; } public long getContentRangeLastPosition() { long range[] = getContentRange(); return range[1]; } public long getContentRangeInstanceLength() { long range[] = getContentRange(); return range[2]; } //////////////////////////////////////////////// // CacheControl //////////////////////////////////////////////// public void setCacheControl(String directive) { setHeader(HTTP.CACHE_CONTROL, directive); } public void setCacheControl(String directive, int value) { String strVal = directive + "=" + Integer.toString(value); setHeader(HTTP.CACHE_CONTROL, strVal); } public String getCacheControl() { return getHeaderValue(HTTP.CACHE_CONTROL); } public void setCacheControl(int value) { setCacheControl(HTTP.MAX_AGE, value); } //////////////////////////////////////////////// // Server //////////////////////////////////////////////// public String getServer() { return getHeaderValue(HTTP.SERVER); } public void setServer(String name) { setHeader(HTTP.SERVER, name); } //////////////////////////////////////////////// // Host //////////////////////////////////////////////// public void setHost(String host, int port) { String hostAddr = host; if (HostInterface.isIPv6Address(host) == true) hostAddr = "[" + host + "]"; setHeader(HTTP.HOST, hostAddr + ":" + Integer.toString(port)); } public String getHost() { return getHeaderValue(HTTP.HOST); } public void setHost(String host) { String hostAddr = host; if (HostInterface.isIPv6Address(host) == true) hostAddr = "[" + host + "]"; setHeader(HTTP.HOST, hostAddr); } //////////////////////////////////////////////// // Date //////////////////////////////////////////////// public String getDate() { return getHeaderValue(HTTP.DATE); } public void setDate(Calendar cal) { Date date = new Date(cal); setHeader(HTTP.DATE, date.getDateString()); } //////////////////////////////////////////////// // Connection //////////////////////////////////////////////// public boolean hasTransferEncoding() { return hasHeader(HTTP.TRANSFER_ENCODING); } public String getTransferEncoding() { return getHeaderValue(HTTP.TRANSFER_ENCODING); } public void setTransferEncoding(String value) { setHeader(HTTP.TRANSFER_ENCODING, value); } public boolean isChunked() { if (hasTransferEncoding() == false) return false; String transEnc = getTransferEncoding(); if (transEnc == null) return false; return transEnc.equalsIgnoreCase(HTTP.CHUNKED); } //////////////////////////////////////////////// // set //////////////////////////////////////////////// /* * public final static boolean parse(HTTPPacket httpPacket, InputStream in) * { try { BufferedReader reader = new BufferedReader(new * InputStreamReader(in)); return parse(httpPacket, reader); } catch * (Exception e) { Debug.warning(e); } return false; } */ }