/* * This file is part of the OWASP Proxy, a free intercepting proxy library. * Copyright (C) 2008-2010 Rogan Dawes <rogan@dawes.za.net> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to: * The Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ package org.owasp.proxy.http.server; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.owasp.proxy.http.MessageFormatException; import org.owasp.proxy.http.MessageUtils; import org.owasp.proxy.http.NamedValue; import org.owasp.proxy.http.RequestHeader; import org.owasp.proxy.http.ResponseHeader; import org.owasp.proxy.http.StreamingRequest; import org.owasp.proxy.http.StreamingResponse; import org.owasp.proxy.http.dao.ConversationSummary; import org.owasp.proxy.http.dao.MessageDAO; import org.owasp.proxy.util.AsciiString; import org.springframework.dao.DataAccessException; @SuppressWarnings("serial") public class ConversationServiceHttpRequestHandler implements HttpRequestHandler { private static final byte[] SUCCESS_XML = AsciiString .getBytes("HTTP/1.0 200 Ok\r\nContent-Type: text/xml\r\n\r\n"); private static final byte[] SUCCESS_OCTET = AsciiString .getBytes("HTTP/1.0 200 Ok\r\nContent-Type: application/octet-stream\r\n\r\n"); private static final byte[] SUCCESS_HTML = AsciiString .getBytes("HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\n\r\n"); private static final String CONVERSATIONS = "/conversations"; private static final String SUMMARIES = "/summaries"; private static final String SUMMARY = "/summary"; private static final String REQUEST_HEADER = "/requestHeader"; private static final String RESPONSE_HEADER = "/responseHeader"; private static final String REQUEST_CONTENT = "/requestContent"; private static final String RESPONSE_CONTENT = "/responseContent"; private static final String INDEX_PAGE = "<html><a href='" + CONVERSATIONS + "'>Conversations</a><p>" + "<form target='" + CONVERSATIONS + "'>Since: <input type='text' name='since'></input></form><p>" + "</html>"; private String hostname; private MessageDAO dao; private HttpRequestHandler next; private Map<Integer, ConversationSummary> summaryCache = new LinkedHashMap<Integer, ConversationSummary>() { protected boolean removeEldestEntry( Map.Entry<Integer, ConversationSummary> eldest) { return size() > 1000; } }; public ConversationServiceHttpRequestHandler(String hostname, MessageDAO dao, HttpRequestHandler next) { this.hostname = hostname; this.dao = dao; this.next = next; } /* * (non-Javadoc) * * @see org.owasp.proxy.daemon.HttpRequestHandler#dispose() */ public void dispose() throws IOException { if (next != null) next.dispose(); } /* * (non-Javadoc) * * @see org.owasp.proxy.daemon.HttpRequestHandler#handleRequest(java.net.InetAddress , * org.owasp.httpclient.StreamingRequest) */ public StreamingResponse handleRequest(InetAddress source, StreamingRequest request, boolean isContinue) throws IOException, MessageFormatException { if (next == null || (hostname != null && hostname.equals(request.getTarget() .getHostName()))) { return handleLocalRequest(request); } return next.handleRequest(source, request, isContinue); } private StreamingResponse handleLocalRequest(StreamingRequest request) { try { String resource = request.getResource(); if ("/".equals(resource)) return getIndexPage(); int q = resource.indexOf('?'); NamedValue[] parameters = null; if (q > -1) { parameters = NamedValue.parse(resource.substring(q + 1), "&", "="); resource = resource.substring(0, q); } if (resource.equals(CONVERSATIONS)) { String since = NamedValue.findValue(parameters, "since"); if (since != null) return listConversations(Integer.parseInt(since)); return listConversations(0); } else if (resource.equals(SUMMARIES)) { String since = NamedValue.findValue(parameters, "since"); if (since != null) return getSummaries(Integer.parseInt(since)); return getSummaries(0); } else if (resource.equals(SUMMARY)) { String id = NamedValue.findValue(parameters, "id"); if (id != null) return getSummary(Integer.parseInt(id)); } else if (resource.equals(REQUEST_HEADER)) { String id = NamedValue.findValue(parameters, "id"); if (id != null) return getRequestHeader(Integer.parseInt(id)); } else if (resource.equals(RESPONSE_HEADER)) { String id = NamedValue.findValue(parameters, "id"); if (id != null) return getResponseHeader(Integer.parseInt(id)); } else if (resource.equals(REQUEST_CONTENT)) { String id = NamedValue.findValue(parameters, "id"); String decode = NamedValue.findValue(parameters, "decode"); if (id != null) return getRequestContent(Integer.parseInt(id), "true" .equals(decode)); } else if (resource.equals(RESPONSE_CONTENT)) { String id = NamedValue.findValue(parameters, "id"); String decode = NamedValue.findValue(parameters, "decode"); if (id != null) return getResponseContent(Integer.parseInt(id), "true" .equals(decode)); } } catch (MessageFormatException mfe) { mfe.printStackTrace(); return err_400(); } catch (DataAccessException dae) { dae.printStackTrace(); return err_500(); } catch (NumberFormatException nfe) { nfe.printStackTrace(); return err_400(); } return err_404(); } private StreamingResponse getIndexPage() { return conversation(SUCCESS_HTML, INDEX_PAGE); } private ConversationSummary loadConversationSummary(int id) { ConversationSummary cs = summaryCache.get(id); if (cs != null) return cs; cs = dao.getConversationSummary(id); summaryCache.put(id, cs); return cs; } private Iterator<ConversationSummary> getConversationSummaries( Iterator<Integer> ids) { List<ConversationSummary> conversations = new LinkedList<ConversationSummary>(); while (ids.hasNext()) { conversations.add(loadConversationSummary(ids.next())); } return conversations.iterator(); } private StreamingResponse listConversations(int since) { Iterator<Integer> it = dao.listConversationsSince(since).iterator(); StringBuilder buff = new StringBuilder(); buff.append("<conversations>"); while (it.hasNext()) { buff.append("<conversation>").append(it.next()).append( "</conversation>"); } buff.append("</conversations>"); return conversation(SUCCESS_XML, buff.toString()); } private StreamingResponse getSummary(int id) { ConversationSummary summary = loadConversationSummary(id); StringBuilder buff = new StringBuilder(); buff.append("<summaries>"); xml(buff, summary); buff.append("</summaries>"); return conversation(SUCCESS_XML, buff.toString()); } private StreamingResponse getSummaries(int since) { Iterator<Integer> ids = dao.listConversationsSince(since).iterator(); Iterator<ConversationSummary> it = getConversationSummaries(ids); StringBuilder buff = new StringBuilder(); buff.append("<summaries>"); while (it.hasNext()) { xml(buff, it.next()); } buff.append("</summaries>"); return conversation(SUCCESS_XML, buff.toString()); } private void xml(StringBuilder buff, ConversationSummary summary) { buff.append("<summary id=\""); buff.append(summary.getId()); buff.append("\" requestTime=\"").append(summary.getRequestSubmissionTime()); buff.append("\" responseHeaderTime=\"").append( summary.getResponseHeaderTime()); buff.append("\" responseContentTime=\"").append( summary.getResponseContentTime()); buff.append("\">"); tag(buff, "host", summary.getTarget().getHostName()); tag(buff, "port", summary.getTarget().getPort()); tag(buff, "ssl", summary.isSsl()); tag(buff, "resource", summary.getRequestResource()); tag(buff, "RequestContentType", summary.getRequestContentType()); tag(buff, "RequestContentSize", summary.getRequestContentSize()); tag(buff, "status", summary.getResponseStatus()); tag(buff, "reason", summary.getResponseReason()); tag(buff, "ResponseContentType", summary.getResponseContentType()); tag(buff, "RequestContentSize", summary.getRequestContentSize()); buff.append("</summary>"); } private void tag(StringBuilder buff, String tagname, String content) { if (content == null) return; buff.append("<").append(tagname).append(">"); buff.append(e(content)); buff.append("</").append(tagname).append(">"); } private void tag(StringBuilder buff, String tagname, boolean content) { buff.append("<").append(tagname).append(">"); buff.append(content); buff.append("</").append(tagname).append(">"); } private void tag(StringBuilder buff, String tagname, int content) { if (content <= 0) return; buff.append("<").append(tagname).append(">"); buff.append(content); buff.append("</").append(tagname).append(">"); } private static String e(String s) { StringBuilder buf = new StringBuilder(); int len = (s == null ? -1 : s.length()); for (int i = 0; i < len; i++) { char c = s.charAt(i); if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9') { buf.append(c); } else if (c == '<') { buf.append("<"); } else if (c == '>') { buf.append(">"); } else if (c == '\'') { buf.append("'"); } else if (c == '"') { buf.append("""); } else { buf.append("&#" + (int) c + ";"); } } return buf.toString(); } private StreamingResponse getRequestHeader(int id) throws MessageFormatException { RequestHeader r = dao.loadRequestHeader(id); if (r == null) return err_404(); return conversation(SUCCESS_OCTET, r.getHeader()); } private StreamingResponse getResponseHeader(int id) throws MessageFormatException { ResponseHeader r = dao.loadResponseHeader(id); if (r == null) return err_404(); return conversation(SUCCESS_OCTET, r.getHeader()); } private StreamingResponse getRequestContent(int id, boolean decode) throws MessageFormatException { int contentId = dao.getMessageContentId(id); if (contentId == -1) return err_404(); byte[] content = dao.loadMessageContent(contentId); if (decode) { RequestHeader request = dao.loadRequestHeader(id); try { return conversation(SUCCESS_OCTET, MessageUtils.decode(request, new ByteArrayInputStream(content))); } catch (IOException ioe) { return err_500(); } } return conversation(SUCCESS_OCTET, content); } private StreamingResponse getResponseContent(int id, boolean decode) throws MessageFormatException { int contentId = dao.getMessageContentId(id); if (contentId == -1) return err_404(); byte[] content = dao.loadMessageContent(contentId); if (decode) { ResponseHeader response = dao.loadResponseHeader(id); try { return conversation(SUCCESS_OCTET, MessageUtils.decode( response, new ByteArrayInputStream(content))); } catch (IOException ioe) { return err_500(); } } return conversation(SUCCESS_OCTET, content); } private StreamingResponse err_400() { return conversation("HTTP/1.0 400 Bad request\r\n\r\n", "Bad request"); } private StreamingResponse err_404() { return conversation("HTTP/1.0 404 Resource not found\r\n\r\n", "Resource not found"); } private StreamingResponse err_500() { return conversation("HTTP/1.0 500 Error processing request\r\n\r\n", "Error processing request"); } private StreamingResponse conversation(String header, String content) { return conversation(AsciiString.getBytes(header), content); } private StreamingResponse conversation(byte[] header, String content) { return conversation(header, stream(content)); } private StreamingResponse conversation(byte[] header, byte[] content) { return conversation(header, stream(content)); } private StreamingResponse conversation(byte[] header, InputStream content) { StreamingResponse response = new StreamingResponse.Impl(); response.setHeader(header); response.setContent(content); return response; } private InputStream stream(byte[] content) { return new ByteArrayInputStream(content); } private InputStream stream(String content) { return stream(AsciiString.getBytes(content)); } }