/* * Copyright 2010 NCHOVY * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.krakenapps.pcap.decoder.http.impl; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import javax.mail.util.SharedByteArrayInputStream; import org.krakenapps.pcap.decoder.http.HttpHeaders; import org.krakenapps.pcap.decoder.http.HttpResponse; import org.krakenapps.pcap.decoder.http.HttpVersion; import org.krakenapps.pcap.util.Buffer; import org.krakenapps.pcap.util.ChainBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author mindori */ public class HttpResponseImpl implements HttpResponse { private final Logger logger = LoggerFactory.getLogger(HttpResponseImpl.class.getName()); private Buffer binary; private HttpVersion httpVersion; private int statusCode; private String reasonPhrase; private Map<String, String> headers; /* flags represent to content type of http */ private EnumSet<FlagEnum> flags = EnumSet.of(FlagEnum.NONE); /* NORMAL variable */ private int putLength = 0; /* MULTIPART, BYTERANGE variable */ private String boundary; private int partLength = -1; /* GZIP variable */ private int gzipOffset = 0; private int gzipLength = -1; /* CHUNKED variable */ private int chunkedOffset = 0; private int chunkedLength = -1; private Buffer contentBuffer; private Buffer gzipBuf; private List<Byte> gzipContent; private List<Byte> chunked; private Buffer chunkedBuf; // private String contentStr; private byte[] content; private byte[] decompressedGzip; private byte[] chunkedBytes; private String textContent; private InputStream inputStream; private MimeMessage message; public HttpResponseImpl() { binary = new ChainBuffer(); headers = new HashMap<String, String>(); } public void putBinary(Buffer data) { binary.addLast(data); } public Buffer getBinary() { return binary; } @Override public HttpVersion getHttpVersion() { return httpVersion; } public void setHttpVersion(String httpVersion) { if (httpVersion.equals("HTTP/1.1")) this.httpVersion = HttpVersion.HTTP_1_1; else this.httpVersion = HttpVersion.HTTP_1_0; } @Override public int getStatusCode() { return statusCode; } @Override public String getStatusLine() { return statusCode + " " + reasonPhrase; } public void setReasonPhrase(String reasonPhrase) { this.reasonPhrase = reasonPhrase; } public void setStatusCode(int statusCode) { this.statusCode = statusCode; } @Override public Set<String> getHeaderKeys() { return headers.keySet(); } @Override public String getHeader(String name) { if (headers.containsKey(name)) return headers.get(name); return null; } public void addHeader(String header) { String[] token = header.split(": "); String headerName = HttpHeaders.canonicalize(token[0]); if(token.length <= 1) { headers.put(headerName, ""); } else if( token[1] == null ) { headers.put(headerName, ""); } else { headers.put(headerName, token[1]); } } public EnumSet<FlagEnum> getFlag() { return flags; } public int getPutLength() { return putLength; } public void addPutLength(int putLength) { this.putLength += putLength; } public String getBoundary() { return boundary; } public void setBoundary(String boundary) { this.boundary = boundary; } public int getPartLength() { return partLength; } public void setPartLength(int partLength) { this.partLength = partLength; } public int getGzipOffset() { return gzipOffset; } public void setGzipOffset(int gzipOffset) { this.gzipOffset = gzipOffset; } public int getGzipLength() { return gzipLength; } public void setGzipLength(int gzipLength) { this.gzipLength = gzipLength; } public int getChunkedOffset() { return chunkedOffset; } public void setChunkedOffset(int chunkedOffset) { this.chunkedOffset = chunkedOffset; } public int getChunkedLength() { return chunkedLength; } public void setChunkedLength(int chunkedLength) { this.chunkedLength = chunkedLength; } public void createContent() { contentBuffer = new ChainBuffer(); } public Buffer getContentBuffer() { return contentBuffer; } public void createGzip() { gzipContent = new ArrayList<Byte>(); } public List<Byte> getGzip() { return gzipContent; } public void putGzip(byte b) { gzipContent.add(b); } public void putGzip(List<Byte> b) { gzipContent.addAll(b); } public Buffer getGzipBuf() { return gzipBuf; } public void putGzipBuf(byte[] b) { gzipBuf.addLast(b); } public void createChunked() { chunked = new ArrayList<Byte>(); } public List<Byte> getChunked() { return chunked; } public void putChunked(List<Byte> bList) { chunked.addAll(chunkedOffset, bList); } public Buffer getChunkedBuf() { return chunkedBuf; } public void putChunkedBuf(byte[] b) { chunkedBuf.addLast(b); } public void setContent(byte[] content) { this.content = content; } public void setDecompressedGzip(byte[] decompressedGzip) { this.decompressedGzip = decompressedGzip; } public void setChunked(byte[] chunkedBytes) { this.chunkedBytes = chunkedBytes; } public MimeMessage getMimeMessage() { return message; } public void setMessage(MimeMessage message) { this.message = message; } public InputStream getInputStream() { return inputStream; } @Override public String getContent() { return textContent; } public void setContent() { String type = headers.get(HttpHeaders.CONTENT_TYPE); String charset = null; /* try to extract character set from 'Content-Type' field */ if (type != null) { int charsetPos = type.indexOf("charset="); int boundary = type.indexOf(";"); if (charsetPos != -1) charset = type.substring(charsetPos + 8); if (boundary != -1) type = type.substring(0, boundary); } mappingContents(type, charset); } private void mappingContents(String type, String charset) { if (compareContentType(type)) { if (flags.contains(FlagEnum.GZIP)) { try { if (decompressedGzip == null) { /* decompress failed */ if (logger.isDebugEnabled()) logger.debug("kraken http decoder: gzip decoding failed"); textContent = null; } else { if (charset != null) textContent = new String(decompressedGzip, charset); else { Charset ch = extractCharset(decompressedGzip); if (ch != null) textContent = new String(decompressedGzip, ch); else textContent = new String(decompressedGzip, Charset.defaultCharset()); } } } catch (UnsupportedEncodingException e) { if (logger.isDebugEnabled()) logger.debug("kraken http decoder: unsupported encoding", e); } } else if (flags.contains(FlagEnum.CHUNKED)) { try { /* added code */ if (message.getContent() instanceof SharedByteArrayInputStream) { inputStream = new ByteArrayInputStream(chunkedBytes); } /* added code end */ if (charset != null) textContent = new String(chunkedBytes, charset); else { Charset ch = extractCharset(chunkedBytes); if (ch != null) textContent = new String(chunkedBytes, ch); else textContent = new String(chunkedBytes, Charset.defaultCharset()); } } catch (UnsupportedEncodingException e) { if (logger.isDebugEnabled()) logger.debug("kraken http decoder: unsupported encoding", e); } catch (IOException e) { e.printStackTrace(); } catch (MessagingException e) { e.printStackTrace(); } } else if (flags.contains(FlagEnum.BYTERANGE)) { if (content != null) textContent = new String(content); } else if (flags.contains(FlagEnum.NORMAL)) { try { if (content == null) return; if (charset != null) textContent = new String(content, charset); else { Charset ch = extractCharset(content); if (ch != null) textContent = new String(content, ch); else textContent = new String(content, Charset.defaultCharset()); } } catch (UnsupportedEncodingException e) { if (logger.isDebugEnabled()) logger.debug("kraken http decoder: unsupported encoding", e); } } } } /* try to extract character set from <META> tag */ private Charset extractCharset(byte[] content) { if (content == null) return null; String s; if (content.length > 1024) s = new String(content, 0, 1024); else if (content.length > 200) s = new String(content, 0, 200); else return null; /* avoid upper case characters */ s = s.toLowerCase(); String charset = parseCharset(s, "<meta"); if (charset == null) { charset = parseCharset(s, "<script"); if (charset == null) { charset = parseCharsetFromCss(s); } } if (charset != null) { try { return Charset.forName(charset.replaceAll("\"", "").replaceAll("/", "").trim()); } catch (IllegalCharsetNameException e) { return null; } } return null; } private String parseCharset(String content, String indexStr) { int i = content.indexOf(indexStr); if (i != -1) { int j = content.indexOf("charset"); if (j != -1) { int k = j + 8; while (k < content.length()) { if (content.charAt(k) == '"' || content.charAt(k) == '>') break; k++; } return content.substring(j + 8, k); } } return null; } private String parseCharsetFromCss(String content) { int j = content.indexOf("@charset"); if (j != -1) { int k = j + 10; while (k < content.length()) { if (content.charAt(k) == '"') break; k++; } return content.substring(j + 10, k); } return null; } private boolean compareContentType(String type) { if(type == null) return false; List<String> contentTypes = new ArrayList<String>(); contentTypes.add("text/css"); contentTypes.add("text/html"); contentTypes.add("text/javascript"); contentTypes.add("text/plain"); contentTypes.add("text/xml"); contentTypes.add("application/x-javascript"); contentTypes.add("application/javascript"); contentTypes.add("application/xml"); contentTypes.add("application/octet-stream"); return contentTypes.contains(type); } }