/* * Copyright (C) 2010-2016 JPEXS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.jpexs.decompiler.flash.gui.proxy; import com.jpexs.decompiler.flash.RetryTask; import com.jpexs.decompiler.flash.RunnableIOEx; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.gui.AppFrame; import com.jpexs.decompiler.flash.gui.AppStrings; import com.jpexs.decompiler.flash.gui.GuiAbortRetryIgnoreHandler; import com.jpexs.decompiler.flash.gui.Main; import com.jpexs.decompiler.flash.gui.MainFrame; import com.jpexs.decompiler.flash.gui.View; import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; import com.jpexs.helpers.Helper; import com.jpexs.helpers.utf8.Utf8InputStreamReader; import com.jpexs.helpers.utf8.Utf8OutputStreamWriter; import com.jpexs.proxy.CatchedListener; import com.jpexs.proxy.ReplacedListener; import com.jpexs.proxy.Replacement; import com.jpexs.proxy.Server; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.Image; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.SwingConstants; import javax.swing.filechooser.FileFilter; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableColumnModel; import javax.swing.table.DefaultTableModel; /** * Frame with Proxy * * @author JPEXS */ public class ProxyFrame extends AppFrame implements CatchedListener, MouseListener, ReplacedListener { private static final String REPLACEMENTS_NAME = "replacements.cfg"; private JTable replacementsTable; private JButton switchButton = new JButton(translate("proxy.start")); private boolean started = false; private JTextField portField = new JTextField("55555"); private JCheckBox sniffSWFCheckBox = new JCheckBox("SWF", false); private JCheckBox sniffOSCheckBox = new JCheckBox("OctetStream", false); private JCheckBox sniffJSCheckBox = new JCheckBox("JS", false); private JCheckBox sniffXMLCheckBox = new JCheckBox("XML", false); /** * Is server running * * @return True when running */ public boolean isRunning() { return started; } /** * Sets port for the proxy * * @param port Port number */ public void setPort(int port) { portField.setText(Integer.toString(port)); } private static class SizeItem implements Comparable<SizeItem> { String file; public SizeItem(String file) { this.file = file; } @Override public String toString() { return Helper.byteCountStr(new File(file).length(), false); } @Override public int compareTo(SizeItem o) { return (int) (new File(file).length() - new File(o.file).length()); } } DefaultTableModel tableModel; private SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss"); /** * List of replacements */ private static List<Replacement> replacements = new ArrayList<>(); /** * Saves replacements to file for future use */ private static void saveReplacements() { String replacementsFile = getReplacementsFile(); if (replacements.isEmpty()) { File rf = new File(replacementsFile); if (rf.exists()) { if (!rf.delete()) { Logger.getLogger(ProxyFrame.class.getName()).log(Level.SEVERE, "Cannot delete replacements file"); } } } else { try (PrintWriter pw = new PrintWriter(new Utf8OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(replacementsFile))))) { for (Replacement r : replacements) { pw.println(r.urlPattern); pw.println(r.targetFile); } } catch (IOException ex) { Logger.getLogger(ProxyFrame.class.getName()).log(Level.SEVERE, "Exception during saving replacements", ex); } } } /** * Load replacements from file */ private static void loadReplacements() { String replacementsFile = getReplacementsFile(); if (!(new File(replacementsFile)).exists()) { return; } replacements = new ArrayList<>(); try (BufferedReader br = new BufferedReader(new Utf8InputStreamReader(new FileInputStream(replacementsFile)))) { String s; while ((s = br.readLine()) != null) { Replacement r = new Replacement(s, br.readLine()); replacements.add(r); } } catch (IOException e) { //ignore } } private static String getReplacementsFile() { return Configuration.getFFDecHome() + REPLACEMENTS_NAME; } /** * Constructor * * @param mainFrame Main frame */ public ProxyFrame(final MainFrame mainFrame) { final String[] columnNames = new String[]{ translate("column.accessed"), translate("column.size"), translate("column.url")}; loadReplacements(); Object[][] data = new Object[replacements.size()][3]; for (int i = 0; i < replacements.size(); i++) { Replacement r = replacements.get(i); data[i][0] = r.lastAccess == null ? "" : format.format(r.lastAccess.getTime()); data[i][1] = new SizeItem(r.targetFile); data[i][2] = r.urlPattern; } tableModel = new DefaultTableModel(data, columnNames) { @Override public Class<?> getColumnClass(int columnIndex) { Class[] classes = new Class[]{String.class, SizeItem.class, String.class}; return classes[columnIndex]; } @Override public boolean isCellEditable(int row, int column) { return false; } }; replacementsTable = new JTable(tableModel); DefaultTableCellRenderer tcr = new DefaultTableCellRenderer(); tcr.setHorizontalAlignment(SwingConstants.RIGHT); replacementsTable.setDefaultRenderer(String.class, new DefaultTableCellRenderer()); replacementsTable.setDefaultRenderer(SizeItem.class, tcr); replacementsTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); replacementsTable.setRowSelectionAllowed(true); DefaultTableColumnModel colModel = (DefaultTableColumnModel) replacementsTable.getColumnModel(); colModel.getColumn(0).setMaxWidth(100); colModel.getColumn(1).setMaxWidth(200); replacementsTable.setAutoCreateRowSorter(true); replacementsTable.setAutoCreateRowSorter(false); replacementsTable.addMouseListener(this); replacementsTable.setFont(new Font("Monospaced", Font.PLAIN, 12)); switchButton.addActionListener(this::switchStateButtonActionPerformed); Container cnt = getContentPane(); cnt.setLayout(new BorderLayout()); cnt.add(new JScrollPane(replacementsTable), BorderLayout.CENTER); portField.setPreferredSize(new Dimension(80, portField.getPreferredSize().height)); JPanel buttonsPanel = new JPanel(); buttonsPanel.setLayout(new FlowLayout()); buttonsPanel.add(new JLabel(translate("port"))); buttonsPanel.add(portField); buttonsPanel.add(switchButton); cnt.add(buttonsPanel, BorderLayout.NORTH); JPanel buttonsPanel23 = new JPanel(); buttonsPanel23.setLayout(new BoxLayout(buttonsPanel23, BoxLayout.Y_AXIS)); JPanel buttonsPanel21 = new JPanel(new FlowLayout()); JButton openButton = new JButton(translate("open")); openButton.addActionListener(this::openButtonActionPerformed); buttonsPanel21.add(openButton); JButton clearButton = new JButton(translate("clear")); clearButton.addActionListener(this::clearButtonActionPerformed); buttonsPanel21.add(clearButton); JButton renameButton = new JButton(translate("rename")); renameButton.addActionListener(this::renameButtonActionPerformed); buttonsPanel21.add(renameButton); JButton removeButton = new JButton(translate("remove")); removeButton.addActionListener(this::removeButtonActionPerformed); buttonsPanel21.add(removeButton); //JPanel buttonsPanel22 = new JPanel(new FlowLayout()); JButton copyUrlButton = new JButton(translate("copy.url")); copyUrlButton.addActionListener(this::copyUrlButtonActionPerformed); buttonsPanel21.add(copyUrlButton); JButton saveAsButton = new JButton(translate("save.as")); saveAsButton.addActionListener(this::saveAsButtonActionPerformed); buttonsPanel21.add(saveAsButton); JButton replaceButton = new JButton(translate("replace")); replaceButton.addActionListener(this::replaceButtonActionPerformed); buttonsPanel21.add(replaceButton); JPanel buttonsPanel3 = new JPanel(); buttonsPanel3.setLayout(new FlowLayout()); buttonsPanel3.add(new JLabel(translate("sniff"))); buttonsPanel3.add(sniffSWFCheckBox); buttonsPanel3.add(sniffOSCheckBox); //buttonsPanel3.add(sniffJSCheckBox); //buttonsPanel3.add(sniffXMLCheckBox); buttonsPanel23.add(buttonsPanel21); //buttonsPanel23.add(buttonsPanel22); buttonsPanel23.add(buttonsPanel3); cnt.add(buttonsPanel23, BorderLayout.SOUTH); setSize(800, 500); View.centerScreen(this); View.setWindowIcon(this); setTitle(translate("dialog.title")); this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { setVisible(false); Main.removeTrayIcon(); if (mainFrame != null) { if (mainFrame.isVisible()) { return; } } Main.showModeFrame(); } /** * Invoked when a window is iconified. */ @Override public void windowIconified(WindowEvent e) { setVisible(false); } }); List<Image> images = new ArrayList<>(); images.add(View.loadImage("proxy16")); images.add(View.loadImage("proxy32")); setIconImages(images); } private void open() { if (replacementsTable.getSelectedRow() > -1) { Replacement r = replacements.get(replacementsTable.getRowSorter().convertRowIndexToModel(replacementsTable.getSelectedRow())); Main.openFile(r.targetFile, r.urlPattern); } } private String selectExportDir() { JFileChooser chooser = new JFileChooser(); chooser.setCurrentDirectory(new File(Configuration.lastExportDir.get())); chooser.setDialogTitle(translate("export.select.directory")); chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); chooser.setAcceptAllFileFilterUsed(false); if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { final String selFile = Helper.fixDialogFile(chooser.getSelectedFile()).getAbsolutePath(); Configuration.lastExportDir.set(Helper.fixDialogFile(chooser.getSelectedFile()).getAbsolutePath()); return selFile; } return null; } private int[] getSelectedRows() { int[] sel = replacementsTable.getSelectedRows(); for (int i = 0; i < sel.length; i++) { sel[i] = replacementsTable.getRowSorter().convertRowIndexToModel(sel[i]); } return sel; } private void openButtonActionPerformed(ActionEvent evt) { open(); } private void saveAsButtonActionPerformed(ActionEvent evt) { int[] sel = getSelectedRows(); if (sel.length == 1) { Replacement r = replacements.get(sel[0]); JFileChooser fc = new JFileChooser(); fc.setCurrentDirectory(new File(Configuration.lastSaveDir.get())); String n = r.urlPattern; if (n.contains("?")) { n = n.substring(0, n.indexOf('?')); } if (n.contains("/")) { n = n.substring(n.lastIndexOf('/')); } n = Helper.makeFileName(n); fc.setSelectedFile(new File(Configuration.lastSaveDir.get(), n)); String ext = ".swf"; final String extension = ext; FileFilter swfFilter = new FileFilter() { @Override public boolean accept(File f) { return (f.getName().toLowerCase().endsWith(extension)) || (f.isDirectory()); } @Override public String getDescription() { return AppStrings.translate("filter" + extension); } }; fc.setFileFilter(swfFilter); fc.setAcceptAllFileFilterUsed(true); JFrame f = new JFrame(); View.setWindowIcon(f); if (fc.showSaveDialog(f) == JFileChooser.APPROVE_OPTION) { File file = Helper.fixDialogFile(fc.getSelectedFile()); try { Files.copy(new File(r.targetFile).toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (IOException ex) { View.showMessageDialog(this, translate("error.save.as") + "\r\n" + ex.getLocalizedMessage(), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); } } } else { GuiAbortRetryIgnoreHandler handler = new GuiAbortRetryIgnoreHandler(); File exportDir = new File(selectExportDir()); for (int s : sel) { final Replacement r = replacements.get(s); String n = r.urlPattern; if (n.contains("?")) { n = n.substring(0, n.indexOf('?')); } if (n.contains("/")) { n = n.substring(n.lastIndexOf('/')); } n = Helper.makeFileName(n); int c = 2; String n2 = n; while (new File(exportDir, n2).exists()) { if (n.contains(".")) { n2 = n.substring(0, n.lastIndexOf('.')) + c + n.substring(n.lastIndexOf('.')); c++; } else { n2 = n + c + ".swf"; c++; } } final File outfile = new File(exportDir, n2); try { new RetryTask(new RunnableIOEx() { @Override public void run() throws IOException { Files.copy(new File(r.targetFile).toPath(), outfile.toPath(), StandardCopyOption.REPLACE_EXISTING); } }, handler).run(); } catch (IOException | InterruptedException ex) { break; } } } } private void replaceButtonActionPerformed(ActionEvent evt) { int[] sel = getSelectedRows(); if (sel.length > 0) { Replacement r = replacements.get(sel[0]); JFileChooser fc = new JFileChooser(); fc.setCurrentDirectory(new File(Configuration.lastOpenDir.get())); String ext = ".swf"; final String extension = ext; FileFilter swfFilter = new FileFilter() { @Override public boolean accept(File f) { return (f.getName().toLowerCase().endsWith(extension)) || (f.isDirectory()); } @Override public String getDescription() { return AppStrings.translate("filter" + extension); } }; fc.setFileFilter(swfFilter); fc.setAcceptAllFileFilterUsed(true); JFrame f = new JFrame(); View.setWindowIcon(f); if (fc.showOpenDialog(f) == JFileChooser.APPROVE_OPTION) { File file = Helper.fixDialogFile(fc.getSelectedFile()); try { Files.copy(file.toPath(), new File(r.targetFile).toPath(), StandardCopyOption.REPLACE_EXISTING); tableModel.fireTableCellUpdated(sel[0], 1/*size*/); } catch (IOException ex) { View.showMessageDialog(f, translate("error.replace") + "\r\n" + ex.getLocalizedMessage(), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); } } } } private void copyUrlButtonActionPerformed(ActionEvent evt) { int[] sel = getSelectedRows(); StringBuilder copyText = new StringBuilder(); for (int sc : sel) { Replacement r = replacements.get(sc); if (copyText.length() > 0) { copyText.append(System.lineSeparator()); } copyText.append(r.urlPattern); } if (copyText.length() > 0) { Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection stringSelection = new StringSelection(copyText.toString()); clipboard.setContents(stringSelection, null); } } private void renameButtonActionPerformed(ActionEvent evt) { int[] sel = getSelectedRows(); if (sel.length > 0) { Replacement r = replacements.get(sel[0]); String s = View.showInputDialog("URL", r.urlPattern); if (s != null) { r.urlPattern = s; tableModel.setValueAt(s, sel[0], 2/*url*/); } } } private void clearButtonActionPerformed(ActionEvent evt) { for (Replacement r : replacements) { File f; try { f = (new File(Main.tempFile(r.targetFile))); if (f.exists()) { f.delete(); } } catch (IOException ex) { Logger.getLogger(ProxyFrame.class.getName()).log(Level.SEVERE, null, ex); } } tableModel.setRowCount(0); replacements.clear(); saveReplacements(); } private void removeButtonActionPerformed(ActionEvent evt) { int[] sel = getSelectedRows(); Arrays.sort(sel); for (int i = sel.length - 1; i >= 0; i--) { tableModel.removeRow(sel[i]); Replacement r = replacements.remove(sel[i]); saveReplacements(); File f = (new File(r.targetFile)); if (f.exists()) { f.delete(); } } } private void switchStateButtonActionPerformed(ActionEvent evt) { Main.switchProxy(); } /** * Switch proxy state */ public void switchState() { started = !started; if (started) { int port = 0; try { port = Integer.parseInt(portField.getText()); } catch (NumberFormatException nfe) { } if ((port <= 0) || (port > 65535)) { View.showMessageDialog(this, translate("error.port"), translate("error"), JOptionPane.ERROR_MESSAGE); started = false; return; } List<String> catchedContentTypes = new ArrayList<>(); catchedContentTypes.add("application/x-shockwave-flash"); catchedContentTypes.add("application/x-javascript"); catchedContentTypes.add("application/javascript"); catchedContentTypes.add("text/javascript"); catchedContentTypes.add("application/json"); catchedContentTypes.add("text/xml"); catchedContentTypes.add("application/xml"); catchedContentTypes.add("application/octet-stream"); if (!Server.startServer(port, replacements, catchedContentTypes, this, this)) { JOptionPane.showMessageDialog(this, translate("error.start.server").replace("%port%", "" + port), AppStrings.translate("error"), JOptionPane.ERROR_MESSAGE); started = false; return; } switchButton.setText(translate("proxy.stop")); portField.setEditable(false); } else { Server.stopServer(); switchButton.setText(translate("proxy.start")); portField.setEditable(true); } } /** * Mouse clicked event * * @param e event */ @Override public void mouseClicked(MouseEvent e) { if (e.getSource() == replacementsTable) { if (e.getClickCount() == 2) { open(); } } } /** * Mouse pressed event * * @param e event */ @Override public void mousePressed(MouseEvent e) { } /** * Mouse released event * * @param e event */ @Override public void mouseReleased(MouseEvent e) { } /** * Mouse entered event * * @param e event */ @Override public void mouseEntered(MouseEvent e) { } /** * Mouse exited event * * @param e event */ @Override public void mouseExited(MouseEvent e) { } /** * Method called when specified contentType is received * * @param contentType Content type * @param url URL of the method * @param data Data stream * @return replacement data */ @Override public byte[] catched(String contentType, String url, InputStream data) { boolean swfOnly = false; if (contentType.contains(";")) { contentType = contentType.substring(0, contentType.indexOf(';')); } if ((!sniffSWFCheckBox.isSelected()) && (contentType.equals("application/x-shockwave-flash"))) { return null; } if ((!sniffJSCheckBox.isSelected()) && (contentType.equals("application/javascript") || contentType.equals("application/x-javascript") || contentType.equals("text/javascript") || contentType.equals("application/json"))) { return null; } if ((!sniffXMLCheckBox.isSelected()) && (contentType.equals("application/xml") || contentType.equals("text/xml"))) { return null; } if ((!sniffOSCheckBox.isSelected()) && (contentType.equals("application/octet-stream"))) { return null; } byte[] result = null; boolean cont = false; for (Replacement r : replacements) { if (r.matches(url)) { cont = true; break; } } if (!cont) { try { byte[] hdr = new byte[3]; if (data.read(hdr) != 3) { throw new IOException(); } String shdr = new String(hdr); if (swfOnly && ((!shdr.equals("FWS")) && (!shdr.equals("CWS")) && (!shdr.equals("ZWS")))) { return null; //NOT SWF } String tempFilePath = Main.tempFile(url); data.reset(); byte[] dataArray = Helper.readStream(data); try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(new File(tempFilePath)))) { fos.write(dataArray); } result = SWFDecompilerPlugin.fireProxyFileCatched(dataArray); Replacement r = new Replacement(url, tempFilePath); r.lastAccess = Calendar.getInstance(); replacements.add(r); saveReplacements(); tableModel.addRow(new Object[]{ r.lastAccess == null ? "" : format.format(r.lastAccess.getTime()), new SizeItem(r.targetFile), r.urlPattern }); } catch (IOException e) { } } return result; } /** * Shows or hides this {@code Window} depending on the value of parameter * {@code b}. * * @param b if {@code true}, makes the {@code Window} visible, otherwise * hides the {@code Window}. If the {@code Window} and/or its owner are not * yet displayable, both are made displayable. The {@code Window} will be * validated prior to being made visible. If the {@code Window} is already * visible, this will bring the {@code Window} to the front.<p> * If {@code false}, hides this {@code Window}, its subcomponents, and all * of its owned children. The {@code Window} and its subcomponents can be * made visible again with a call to {@code #setVisible(true)}. * @see java.awt.Component#isDisplayable * @see java.awt.Component#setVisible * @see java.awt.Window#toFront * @see java.awt.Window#dispose */ @Override public void setVisible(boolean b) { if (b == true) { Main.addTrayIcon(); } super.setVisible(b); } @Override public void replaced(Replacement replacement, String url, String contentType) { int index = replacements.indexOf(replacement); tableModel.setValueAt(replacement.lastAccess == null ? "" : format.format(replacement.lastAccess.getTime()), index, 0); tableModel.setValueAt(new SizeItem(replacement.targetFile), index, 1); tableModel.setValueAt(replacement.urlPattern, index, 2); } }