/** * Copyright 2011 Intuit Inc. All Rights Reserved */ package com.intuit.tank.util; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.intuit.tank.conversation.Cookie; import com.intuit.tank.conversation.Header; import com.intuit.tank.conversation.Protocol; import com.intuit.tank.conversation.Request; import com.intuit.tank.conversation.Response; /** * HeaderParseUtil * * @author dangleton * */ public class HeaderParser { private static final Logger LOG = LogManager.getLogger(HeaderParser.class); // HTTP/1.0 200 OK private static final int RESPONSE_STATUS_CODE_INDEX = 1; private static final int RESPONSE_STATUS_MSG_INDEX = 2; private static final int RESPONSE_PROTOCOL_INDEX = 0; // GET /path/to/file/index.html HTTP/1.0 private static final int REQUEST_PROTOCOL_INDEX = 2; private static final int REQUEST_METHOD_INDEX = 0; private static final int REQUEST_PATH_INDEX = 1; private Map<String, List<String>> headerMap = new HashMap<String, List<String>>(); private Collection<Header> headers; private HeaderType headerType; private String[] firstLine; private DateFormat expiresFormat = new SimpleDateFormat("EE, dd-MMM-yyyy HH:mm:ss z"); private DateFormat responseDateFormat = new SimpleDateFormat("EE, dd MMM yyyy HH:mm:ss z"); public HeaderParser(Request request) { this(HeaderType.Request, request.getFirstLine(), request.getHeaders()); } public HeaderParser(Response response) { this(HeaderType.Response, response.getFirstLine(), response.getHeaders()); } public HeaderParser(HeaderType type, String firstLine, Collection<Header> headers) { this.headerType = type; this.headers = headers; this.firstLine = firstLine != null ? firstLine.split("\\s") : new String[0]; for (Header header : headers) { List<String> list = headerMap.get(header.getKey().toLowerCase()); if (list == null) { list = new ArrayList<String>(); headerMap.put(header.getKey().toLowerCase(), list); } String value = header.getValue(); if (this.headerType == HeaderType.Request && header.getKey().equalsIgnoreCase("Authorization")) { int index = value.indexOf(':'); if (index != -1) { value = value.substring(0, index) + " tank removed auth info"; } } list.add(value); } } /** * * @param line * @return */ public static String extractPath(String line) { String ret = null; String[] lines = line.split("\\s"); if (lines.length > REQUEST_PATH_INDEX) { ret = lines[REQUEST_PATH_INDEX]; if (ret.indexOf('?') != -1) { ret = ret.substring(0, ret.indexOf('?')); } } return ret; } /** * * @param line * @return */ public static String extractLocation(String line) { String ret = null; String[] lines = line.split("\\s"); if (lines.length > REQUEST_PATH_INDEX) { ret = lines[REQUEST_PATH_INDEX]; } return ret; } /** * * @return */ public static int extractStatusCode(String line) { String[] lines = line.split("\\s"); if (lines.length > RESPONSE_STATUS_CODE_INDEX) { return Integer.parseInt(lines[RESPONSE_STATUS_CODE_INDEX]); } return 0; } /** * @return */ public List<Cookie> getCookies() { if (headerType == HeaderType.Request) { return getRequestCookies(); } return getResponseCookies(); } /** * * @return */ public int getStatusCode() { if (headerType == HeaderType.Response && firstLine.length > RESPONSE_STATUS_CODE_INDEX) { return Integer.parseInt(firstLine[RESPONSE_STATUS_CODE_INDEX]); } return 0; } public String getUrl(Protocol protocol) { String port = getPort(); return protocol.name() + "://" + getHost() + (StringUtils.isEmpty(port) ? "" : ":" + port) + getFullPath(); } public List<Header> getPassThroughHeaders() { ArrayList<Header> ret = new ArrayList<Header>(headers.size()); for (Header h : headers) { String s = h.getKey().toLowerCase(); if (!s.contains("cookie")) ret.add(h); } ret.addAll(this.extractFirstLineHeaders()); return ret; } public String getRedirectLocation() { return getSingleValue("Location", null); } /** * * @return */ public String getContentType() { return getSingleValue("Content-Type", ""); } /** * * @return */ public String getHost() { String ret = getSingleValue("Host"); if (!StringUtils.isEmpty(ret)) { int ind = ret.indexOf(':'); if (ind != -1) { ret = ret.substring(0, ind); } } return ret; } /** * * @return */ public String getPort() { String ret = getSingleValue("Host"); if (!StringUtils.isEmpty(ret)) { int ind = ret.indexOf(':'); if (ind != -1) { ret = ret.substring(ind + 1); } else { ret = ""; } } return ret; } /** * * @return */ public int getContentLength() { String contentLength = getSingleValue("Content-Length"); if (NumberUtils.isNumber(contentLength)) { return Integer.parseInt(contentLength); } return 0; } /** * * @return */ public List<KeyValuePair> getQueryParams() { String path = firstLine[REQUEST_PATH_INDEX]; List<KeyValuePair> ret = new ArrayList<KeyValuePair>(); if (!StringUtils.isEmpty(path) && path.indexOf('?') != -1) { path = path.substring(path.indexOf('?') + 1); String delim = "\\;"; if (path.indexOf('&') != -1 || path.indexOf(';') == -1) { delim = "\\&"; } String[] split = path.split(delim); for (String keyPair : split) { if (keyPair.indexOf('=') == -1) { if (!StringUtils.isEmpty(keyPair)) { ret.add(new KeyValuePair(keyPair.trim())); } } else { String[] pair = keyPair.split("="); String value = pair.length > 1 ? pair[1].trim() : ""; ret.add(new KeyValuePair(pair[0].trim(), value)); } } } return ret; } /** * * @return */ public List<KeyValuePair> getPostParameters(String body) { List<KeyValuePair> ret = new ArrayList<KeyValuePair>(); if (headerType == HeaderType.Request && !StringUtils.isEmpty(body) && getContentType().toLowerCase().contains("www-form-urlencoded")) { String[] split = body.split("\\&"); for (String keyPair : split) { if (keyPair.indexOf('=') == -1) { ret.add(new KeyValuePair(keyPair.trim())); } else { String[] pair = keyPair.split("="); String value = pair.length > 1 ? pair[1].trim() : ""; ret.add(new KeyValuePair(pair[0].trim(), value)); } } } return ret; } /* * */ public String getStatusMessage() { if (headerType == HeaderType.Response && firstLine.length > RESPONSE_STATUS_MSG_INDEX) { return firstLine[RESPONSE_STATUS_MSG_INDEX]; } return null; } /** * * @return */ public String getHttpVersion() { if (headerType == HeaderType.Response && firstLine.length > RESPONSE_PROTOCOL_INDEX) { return firstLine[RESPONSE_STATUS_MSG_INDEX]; } else if (headerType == HeaderType.Request && firstLine.length > REQUEST_PROTOCOL_INDEX) { return firstLine[REQUEST_PROTOCOL_INDEX]; } return null; } /** * * @return */ public String getPath() { String ret = null; if (headerType == HeaderType.Request && firstLine.length > REQUEST_PATH_INDEX) { ret = firstLine[REQUEST_PATH_INDEX]; if (ret.indexOf('?') != -1) { ret = ret.substring(0, ret.indexOf('?')); } } return ret; } /** * * @return */ public String getFullPath() { String ret = null; if (headerType == HeaderType.Request && firstLine.length > REQUEST_PATH_INDEX) { ret = firstLine[REQUEST_PATH_INDEX]; } return ret; } /** * * @return */ public String getMethod() { if (headerType == HeaderType.Request && firstLine.length > REQUEST_METHOD_INDEX) { return firstLine[REQUEST_METHOD_INDEX]; } return null; } /** * * @return */ public Date getResponseDate() { Date ret = null; if (headerType == HeaderType.Response) { String dateStr = getSingleValue("date"); parseDate(dateStr); } return ret; } /** * @param dateStr */ private Date parseDate(String dateStr) { Date ret = null; if (dateStr != null) { try { ret = responseDateFormat.parse(dateStr); } catch (ParseException e) { try { ret = expiresFormat.parse(dateStr); } catch (ParseException e1) { LOG.warn("Cannot parse date " + dateStr); } } } return ret; } /** * * @param key * @return */ private String getSingleValue(String key) { String ret = null; List<String> list = headerMap.get(key.toLowerCase()); if (list != null && list.size() == 1) { ret = list.get(0); } return ret; } /** * * @param key * @return */ private String getSingleValue(String key, String defaultValue) { String ret = getSingleValue(key); return ret != null ? ret : defaultValue; } /** * @return */ private List<Cookie> getResponseCookies() { List<Cookie> ret = new ArrayList<Cookie>(); List<String> list = headerMap.get("set-cookie"); if (list != null) { for (String cookieValue : list) { Cookie c = new Cookie(); ret.add(c); String[] attributePairs = cookieValue.split(";"); boolean isFirst = true; for (String pair : attributePairs) { if (!StringUtils.isEmpty(pair)) { String[] cookie = pair.split("="); if (cookie.length > 0 && !StringUtils.isEmpty(cookie[0])) { String key = cookie[0].trim(); String value = ""; if (cookie.length > 1) { value = cookie[1].trim(); } if (isFirst) { c.setKey(key); c.setValue(value); isFirst = false; } else if (key.equalsIgnoreCase("Expires")) { c.setExpires(parseDate(value)); } else if (key.equalsIgnoreCase("Max-Age") && NumberUtils.isNumber(value)) { c.setMaxAge(value); } else if (key.equalsIgnoreCase("Path") && !StringUtils.isEmpty(value)) { c.setPath(value); } else if (key.equalsIgnoreCase("Domain") && !StringUtils.isEmpty(value)) { c.setDomain(value); } else if (key.equalsIgnoreCase("Secure")) { c.setSecuredOnly(true); } else if (key.equalsIgnoreCase("HttpOnly")) { c.setHttpOnly(true); } } } } } } return ret; } /** * * @return */ private List<Cookie> getRequestCookies() { List<Cookie> ret = new ArrayList<Cookie>(); List<String> list = headerMap.get("cookie"); if (list != null) { for (String cookieValue : list) { String[] cookiePairs = cookieValue.split(";"); for (String pair : cookiePairs) { if (!StringUtils.isEmpty(pair)) { String[] cookie = pair.split("="); if (cookie.length > 0 && !StringUtils.isEmpty(cookie[0])) { String key = cookie[0].trim(); String value = ""; if (cookie.length > 1) { value = cookie[1].trim(); } ret.add(new Cookie(key, value)); } } } } } return ret; } public List<Header> extractFirstLineHeaders() { List<Header> ret = new ArrayList<Header>(); if (this.headerType.equals(HeaderType.Response)) { ret.add(new Header("X-HTTP_PROTOCOL", this.firstLine[0])); ret.add(new Header("X-HTTP_STATUS_CODE", this.firstLine[1])); ret.add(new Header("X-HTTP_STATUS_MESSAGE", StringUtils.join(Arrays.copyOfRange(this.firstLine, 2, this.firstLine.length)))); } return ret; } public static enum HeaderType { Request, Response } }