/** * Copyright 2011 Intuit Inc. All Rights Reserved */ package com.intuit.tank.proxy; /* * #%L * proxy-extension * %% * Copyright (C) 2011 - 2015 Intuit Inc. * %% * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * #L% */ import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ProxySelector; import java.net.URL; import java.security.GeneralSecurityException; import java.security.Security; import java.util.Arrays; import java.util.List; import java.util.regex.PatternSyntaxException; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.RowFilter; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import org.apache.commons.lang3.StringUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.owasp.proxy.daemon.LoopAvoidingTargetedConnectionHandler; import org.owasp.proxy.daemon.Proxy; import org.owasp.proxy.daemon.ServerGroup; import org.owasp.proxy.daemon.TargetedConnectionHandler; import org.owasp.proxy.http.MutableRequestHeader; import org.owasp.proxy.http.MutableResponseHeader; import org.owasp.proxy.http.RequestHeader; import org.owasp.proxy.http.client.HttpClient; import org.owasp.proxy.http.server.BufferedMessageInterceptor; import org.owasp.proxy.http.server.DefaultHttpRequestHandler; import org.owasp.proxy.http.server.HttpProxyConnectionHandler; import org.owasp.proxy.http.server.HttpRequestHandler; import org.owasp.proxy.socks.SocksConnectionHandler; import org.owasp.proxy.ssl.SSLConnectionHandler; import org.owasp.proxy.ssl.SSLContextSelector; import com.intuit.tank.conversation.Session; import com.intuit.tank.conversation.Transaction; import com.intuit.tank.entity.Application; import com.intuit.tank.handler.BufferingHttpRequestHandler; import com.intuit.tank.proxy.config.ProxyConfiguration; import com.intuit.tank.proxy.settings.ui.ProxyConfigDialog; import com.intuit.tank.proxy.settings.ui.XmlFileFilter; import com.intuit.tank.proxy.table.ShowHostsDialog; import com.intuit.tank.proxy.table.TransactionRecordedListener; import com.intuit.tank.proxy.table.TransactionTable; import com.intuit.tank.proxy.table.TransactionTableModel; import com.intuit.tank.util.WebConversationJaxbParseXML; /** * ProxyApp * * @author dangleton * */ public class ProxyApp extends JFrame implements TransactionRecordedListener { private static final long serialVersionUID = 1L; private Application application; private Proxy p; private TransactionTableModel model; private JDialog detailsDialog; private JFileChooser fileChooser; private JTextArea detailsTF; private AbstractAction startAction; private AbstractAction stopAction; private AbstractAction pauseAction; private AbstractAction settingsAction; private AbstractAction saveAction; private AbstractAction openAction; private AbstractAction filterAction; private ProxyConfigDialog configDialog; private int keyMask; private File currentFile; private boolean isDirty; private ShowHostsDialog showHostsDialog; private AbstractAction showHostsAction; public ProxyApp() { super("Intuit Tank Recording Proxy"); setSize(new Dimension(800, 600)); setBounds(new Rectangle(getSize())); setPreferredSize(getSize()); setDefaultCloseOperation(DISPOSE_ON_CLOSE); setLayout(new BorderLayout()); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { try { stop(); } catch (Exception e1) { e1.printStackTrace(); } System.exit(0); } }); keyMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); configDialog = new ProxyConfigDialog(this); createActions(); this.setJMenuBar(createMenu()); createDetailsDialog(); JPanel tablePanel = getTransactionTable(); this.add(createToolBar(), BorderLayout.NORTH); this.add(tablePanel, BorderLayout.CENTER); WindowUtil.centerOnScreen(this); pack(); setVisible(true); requestFocus(); } /** * @{inheritDoc */ public void transactionProcessed(Transaction t, boolean filtered) { model.addTransaction(t, filtered); } /** * @throws IOException * @throws GeneralSecurityException * */ private void start() throws GeneralSecurityException, IOException { if (application != null && application.isPaused()) { application.resumeSession(); } else { ProxyConfiguration config = configDialog.getConfiguration(); model.clear(); saveAction.setEnabled(false); filterAction.setEnabled(false); application = new Application(config); currentFile = new File(config.getOutputFile()); isDirty = false; InetSocketAddress listen; String proxy = "DIRECT"; listen = new InetSocketAddress("localhost", config.getPort()); final ProxySelector ps = Main.getProxySelector(proxy); DefaultHttpRequestHandler drh = new DefaultHttpRequestHandler() { @Override protected HttpClient createClient() { HttpClient client = super.createClient(); client.setProxySelector(ps); client.setSoTimeout(20000); return client; } }; ServerGroup sg = new ServerGroup(); sg.addServer(listen); drh.setServerGroup(sg); HttpRequestHandler rh = drh; BufferedMessageInterceptor bmi = new BufferedMessageInterceptor() { @Override public Action directResponse(RequestHeader request, MutableResponseHeader response) { return Action.BUFFER; } @Override public Action directRequest(MutableRequestHeader request) { return Action.BUFFER; } }; rh = new BufferingHttpRequestHandler(rh, bmi, 1024 * 1024, application); HttpProxyConnectionHandler hpch = new HttpProxyConnectionHandler(rh); SSLContextSelector cp = Main.getSSLContextSelector(); TargetedConnectionHandler tch = new SSLConnectionHandler(cp, true, hpch); tch = new LoopAvoidingTargetedConnectionHandler(sg, tch); hpch.setConnectHandler(tch); TargetedConnectionHandler socks = new SocksConnectionHandler(tch, true); application.startSession(this); p = new Proxy(listen, socks, null); p.setSocketTimeout(90000); p.start(); System.out.println("Listener started on " + listen); } startAction.setEnabled(false); stopAction.setEnabled(true); pauseAction.setEnabled(true); saveAction.setEnabled(false); filterAction.setEnabled(false); openAction.setEnabled(false); } private void stop() throws JAXBException, IOException { if (p != null) { p.stop(); application.endSession(); p = null; } startAction.setEnabled(true); stopAction.setEnabled(false); pauseAction.setEnabled(false); saveAction.setEnabled(true); filterAction.setEnabled(true); openAction.setEnabled(true); application = null; saveAction.setEnabled(isDirty); } /** * */ private void pause() { application.pauseSession(); pauseAction.setEnabled(false); startAction.setEnabled(true); } /** * */ private void showHosts() { if (showHostsDialog == null) { showHostsDialog = new ShowHostsDialog(this, configDialog); } showHostsDialog.setHosts(model.getHostSet()); showHostsDialog.setVisible(true); } /** * */ private void openRecording() { if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { fileChooser.getSelectedFile(); try { List<Transaction> transactions = new WebConversationJaxbParseXML().parse(new FileReader(fileChooser .getSelectedFile())); model.setTransactions(transactions); saveAction.setEnabled(false); filterAction.setEnabled(true); currentFile = fileChooser.getSelectedFile(); } catch (Exception e) { JOptionPane .showMessageDialog(this, "Error opening recording: " + e, "Error", JOptionPane.ERROR_MESSAGE); } } } /** * */ private void save() { if (currentFile != null) { saveAction.setEnabled(false); JAXBContext ctx; try { ctx = JAXBContext.newInstance(Session.class.getPackage().getName()); Marshaller m = ctx.createMarshaller(); m.setProperty("jaxb.formatted.output", Boolean.TRUE); Session session = new Session(model.getTransactions(), configDialog.getConfiguration() .isFollowRedirects()); System.out.println("outputting to : " + currentFile.getCanonicalPath()); m.marshal(session, currentFile); } catch (Exception e) { JOptionPane.showMessageDialog(this, "Error saving recording: " + e, "Error", JOptionPane.ERROR_MESSAGE); } } } /** * */ private void showSettings() { configDialog.showDialog(); } private void createDetailsDialog() { detailsDialog = new JDialog(this); detailsDialog.setModal(true); detailsDialog.setLayout(new BorderLayout()); detailsTF = new JTextArea(); detailsTF.setEditable(false); JScrollPane scrollPane = new JScrollPane(detailsTF); detailsDialog.add(scrollPane, BorderLayout.CENTER); detailsDialog.setModal(true); detailsDialog.setSize(new Dimension(500, 300)); detailsDialog.setBounds(new Rectangle(getSize())); detailsDialog.setPreferredSize(getSize()); WindowUtil.centerOnScreen(detailsDialog); } private JPanel getTransactionTable() { JPanel frame = new JPanel(new BorderLayout()); model = new TransactionTableModel(); final JTable table = new TransactionTable(model); final TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(model); table.setRowSorter(sorter); final JPopupMenu pm = new JPopupMenu(); JMenuItem item = new JMenuItem("Delete Selected"); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { int[] selectedRows = table.getSelectedRows(); if (selectedRows.length != 0) { int response = JOptionPane.showConfirmDialog(ProxyApp.this, "Are you sure you want to delete " + selectedRows.length + " Transactions?", "Confirm Delete", JOptionPane.YES_NO_OPTION); if (response == JOptionPane.YES_OPTION) { int[] correctedRows = new int[selectedRows.length]; for (int i = selectedRows.length; --i >= 0;) { int row = selectedRows[i]; int index = (Integer) table.getValueAt(row, 0) - 1; correctedRows[i] = index; } Arrays.sort(correctedRows); for (int i = correctedRows.length; --i >= 0;) { int row = correctedRows[i]; Transaction transaction = model.getTransactionForIndex(row); if (transaction != null) { model.removeTransaction(transaction, row); isDirty = true; saveAction.setEnabled(isDirty && !stopAction.isEnabled()); } } } } } }); pm.add(item); table.add(pm); table.addMouseListener(new MouseAdapter() { boolean pressed = false; public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { Point p = e.getPoint(); int row = table.rowAtPoint(p); int index = (Integer) table.getValueAt(row, 0) - 1; Transaction transaction = model.getTransactionForIndex(index); if (transaction != null) { detailsTF.setText(transaction.toString()); detailsTF.setCaretPosition(0); detailsDialog.setVisible(true); } } } /** * @{inheritDoc */ @Override public void mousePressed(MouseEvent e) { if (e.isPopupTrigger()) { pressed = true; int[] selectedRows = table.getSelectedRows(); if (selectedRows.length != 0) { pm.show(e.getComponent(), e.getX(), e.getY()); } } } /** * @{inheritDoc */ @Override public void mouseReleased(MouseEvent e) { if (!pressed && e.isPopupTrigger()) { int[] selectedRows = table.getSelectedRows(); if (selectedRows.length != 0) { pm.show(e.getComponent(), e.getX(), e.getY()); } } } }); JScrollPane pane = new JScrollPane(table); frame.add(pane, BorderLayout.CENTER); JPanel panel = new JPanel(new BorderLayout()); JLabel label = new JLabel("Filter: "); panel.add(label, BorderLayout.WEST); final JLabel countLabel = new JLabel(" Count: 0 "); panel.add(countLabel, BorderLayout.EAST); final JTextField filterText = new JTextField(""); filterText.addKeyListener(new KeyAdapter() { public void keyTyped(KeyEvent e) { String text = filterText.getText(); if (text.length() == 0) { sorter.setRowFilter(null); } else { try { sorter.setRowFilter(RowFilter.regexFilter(text)); countLabel.setText(" Count: " + sorter.getViewRowCount() + " "); } catch (PatternSyntaxException pse) { System.err.println("Bad regex pattern"); } } } }); panel.add(filterText, BorderLayout.CENTER); frame.add(panel, BorderLayout.NORTH); return frame; } private JToolBar createToolBar() { JToolBar ret = new JToolBar(); // ret.setBackground(new Color(111,167,209)); ret.add(Box.createHorizontalStrut(5)); ret.add(createButton(openAction)); ret.add(Box.createHorizontalStrut(5)); ret.add(createButton(saveAction)); ret.addSeparator(); ret.add(Box.createHorizontalStrut(5)); ret.add(createButton(startAction)); ret.add(Box.createHorizontalStrut(5)); ret.add(createButton(stopAction)); ret.add(Box.createHorizontalStrut(5)); ret.add(createButton(pauseAction)); ret.add(Box.createHorizontalStrut(5)); ret.addSeparator(); ret.add(Box.createHorizontalStrut(5)); ret.add(createButton(filterAction)); ret.add(createButton(settingsAction)); ret.addSeparator(); ret.add(Box.createHorizontalStrut(5)); ret.add(createButton(showHostsAction)); ret.add(Box.createHorizontalGlue()); return ret; } private JButton createButton(Action a) { JButton ret = new JButton(a); ret.setText(""); ret.setMargin(new Insets(3, 3, 3, 3)); // ret.setIcon((Icon) a.getValue(Action.SMALL_ICON_KEY)); return ret; } @SuppressWarnings("serial") public JMenuBar createMenu() { JMenuBar ret = new JMenuBar(); JMenu fileMenu = new JMenu("File"); JMenu sessionMenu = new JMenu("Session"); ret.add(fileMenu); ret.add(sessionMenu); fileMenu.add(getMenuItem(openAction)); fileMenu.add(getMenuItem(saveAction)); fileMenu.addSeparator(); fileMenu.add(getMenuItem(filterAction)); fileMenu.add(getMenuItem(settingsAction)); fileMenu.addSeparator(); AbstractAction quitAction = new AbstractAction("Quit") { public void actionPerformed(ActionEvent arg0) { System.exit(0); } }; quitAction.putValue(javax.swing.Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Q, keyMask)); quitAction.putValue(javax.swing.Action.MNEMONIC_KEY, KeyEvent.VK_Q); fileMenu.add(new JMenuItem(quitAction)); sessionMenu.add(getMenuItem(startAction)); sessionMenu.add(getMenuItem(stopAction)); sessionMenu.add(getMenuItem(pauseAction)); sessionMenu.addSeparator(); sessionMenu.add(getMenuItem(showHostsAction)); return ret; } private JMenuItem getMenuItem(Action a) { JMenuItem item = new JMenuItem(a); return item; } /** * */ @SuppressWarnings("serial") private void createActions() { startAction = new AbstractAction("Start Recording", loadImage("icons/16/control_play_blue.png")) { public void actionPerformed(ActionEvent arg0) { try { start(); } catch (Exception e) { JOptionPane.showMessageDialog(ProxyApp.this, "Error statrting proxy: " + e.toString(), "Error", JOptionPane.ERROR_MESSAGE); } } }; startAction.putValue(javax.swing.Action.SHORT_DESCRIPTION, "Start Recording"); stopAction = new AbstractAction("Stop Recording", loadImage("icons/16/control_stop_blue.png")) { public void actionPerformed(ActionEvent arg0) { try { stop(); } catch (Exception e) { JOptionPane.showMessageDialog(ProxyApp.this, "Error stopping proxy: " + e.toString(), "Error", JOptionPane.ERROR_MESSAGE); } } }; stopAction.setEnabled(false); stopAction.putValue(javax.swing.Action.SHORT_DESCRIPTION, "Stop Recording"); pauseAction = new AbstractAction("Pause Recording", loadImage("icons/16/control_pause_blue.png")) { public void actionPerformed(ActionEvent arg0) { pause(); } }; pauseAction.setEnabled(false); pauseAction.putValue(javax.swing.Action.SHORT_DESCRIPTION, "Pause Recording"); settingsAction = new AbstractAction("Settings", loadImage("icons/16/cog.png")) { public void actionPerformed(ActionEvent arg0) { showSettings(); } }; settingsAction.putValue(javax.swing.Action.SHORT_DESCRIPTION, "Settings"); filterAction = new AbstractAction("Run Filters", loadImage("icons/16/filter.png")) { public void actionPerformed(ActionEvent arg0) { filter(); } }; filterAction.putValue(javax.swing.Action.SHORT_DESCRIPTION, "Run Filters"); filterAction.setEnabled(false); saveAction = new AbstractAction("Save", loadImage("icons/16/save_as.png")) { public void actionPerformed(ActionEvent arg0) { save(); } }; saveAction.setEnabled(false); saveAction.putValue(javax.swing.Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, keyMask)); saveAction.putValue(javax.swing.Action.SHORT_DESCRIPTION, "Save"); openAction = new AbstractAction("Open Recording...", loadImage("icons/16/folder_go.png")) { public void actionPerformed(ActionEvent arg0) { openRecording(); } }; openAction.putValue(javax.swing.Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_O, keyMask)); openAction.putValue(javax.swing.Action.SHORT_DESCRIPTION, "Open Recording..."); showHostsAction = new AbstractAction("Hosts...", loadImage("icons/16/page_add.png")) { public void actionPerformed(ActionEvent arg0) { showHosts(); } }; showHostsAction.putValue(javax.swing.Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_H, keyMask)); showHostsAction.putValue(javax.swing.Action.SHORT_DESCRIPTION, "Show Hosts..."); fileChooser = new JFileChooser(new File(".")); fileChooser.setDialogTitle("Open Recording..."); fileChooser.setFileFilter(new XmlFileFilter()); } /** * */ protected void filter() { List<Transaction> transactions = model.getAllTransactions(); model.clear(); for (Transaction t : transactions) { boolean filtered = true; if (Application.shouldInclude(t, configDialog.getInclusions(), configDialog.getExclusions(), false)) { filtered = false; } transactionProcessed(t, filtered); } } private ImageIcon loadImage(String path) { URL url = ClassLoader.getSystemClassLoader().getResource(path); return new ImageIcon(url); } public static void main(String[] args) { if (StringUtils.isBlank(System.getProperty("jsse.enableSNIExtension"))) { System.setProperty("jsse.enableSNIExtension", "false"); } if (StringUtils.isBlank(System.getProperty("https.protocols"))) { System.setProperty("https.protocols", "TLSv1,SSLv3"); } Security.addProvider(new BouncyCastleProvider()); // add it new ProxyApp(); } }