package org.owasp.webscarab.ui.swing.editors;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextPane;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import org.owasp.webscarab.util.swing.JTreeTable;
import org.owasp.webscarab.util.swing.treetable.DefaultTreeTableModel;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.amf.ActionContext;
import flex.messaging.io.amf.ActionMessage;
import flex.messaging.io.amf.AmfMessageDeserializer;
import flex.messaging.io.amf.AmfMessageSerializer;
import flex.messaging.io.amf.AmfTrace;
/**
* Panel for parsing and manipulating AMF0 and AMF3 requests/responses. It is
* currently only possible to change String and long values.
*
* Currently this panels relies on code from Adobe's BlazeDS project.
*
* NB: This class was ported from Java 1.5 and from some of SwingLabs' swingx
* components and may therefore need more testing.
*
* @author Martin Clausen <mclausen@deloitte.dk>
*
*/
public class AMFPanel extends JPanel implements ByteArrayEditor, ActionListener {
/** The parsed ActionMessage object. */
private ActionMessage message;
private ActionContext messageContext;
private SerializationContext serialContext;
/** The AMF encoded message. */
private byte[] messageBytes;
/** The views are organized in tabs. */
private JTabbedPane tabs;
private JTreeTable treeTable;
private JTextPane stringsArea;
private JTextPane hexArea;
private JButton exportButton;
private JFileChooser fc;
public static boolean DEBUG = true;
/** Default constructor. */
public AMFPanel() {
super(new GridLayout(1, 1));
setName("AMF");
// Organize the views in tabs
tabs = new JTabbedPane();
// The following line enables to use scrolling tabs.
tabs.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
}
// ByteArrayEditor METHODS
public String[] getContentTypes() {
return new String[] { "application/x-amf" };
}
public boolean isModified() {
return true;
}
public void setEditable(boolean editable) {
// Ignore
}
public void setBytes(String contentType, byte[] messageBytes) {
this.messageBytes = (byte[]) messageBytes.clone();
parseAMFMessage();
addTreeTable();
updateStringsArea();
updateHexArea();
add(tabs);
}
public byte[] getBytes() {
encodeAMFMessage();
return (byte[]) messageBytes.clone();
}
private void parseAMFMessage() {
try {
serialContext = SerializationContext.getSerializationContext();
serialContext.instantiateTypes = false;
AmfTrace trace = null;
if (DEBUG)
trace = new AmfTrace();
AmfMessageDeserializer amfder = new AmfMessageDeserializer();
amfder.initialize(serialContext, new ByteArrayInputStream(
messageBytes), trace);
message = new ActionMessage();
messageContext = new ActionContext();
amfder.readMessage(message, messageContext);
if (DEBUG)
System.err.println(trace);
} catch (Exception x) {
x.printStackTrace();
}
}
private void encodeAMFMessage() {
try {
AmfTrace trace = null;
if (DEBUG)
trace = new AmfTrace();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
AmfMessageSerializer amfser = new AmfMessageSerializer();
amfser.initialize(serialContext, baos, trace);
amfser.writeMessage(message);
messageBytes = baos.toByteArray();
if (DEBUG) {
System.out.println(dump("", messageBytes, 0,
messageBytes.length));
System.err.println(trace);
}
} catch (Exception x) {
x.printStackTrace();
}
}
private static byte[] encodeAMFMessage(ActionMessage message) {
byte[] messageBytes = null;
try {
AmfTrace trace = null;
if (DEBUG)
trace = new AmfTrace();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
AmfMessageSerializer amfser = new AmfMessageSerializer();
SerializationContext context = SerializationContext
.getSerializationContext();
context.instantiateTypes = false;
amfser.initialize(context, baos, trace);
amfser.writeMessage(message);
messageBytes = baos.toByteArray();
if (DEBUG) {
System.out.println(dump("", messageBytes, 0,
messageBytes.length));
System.err.println(trace);
}
} catch (Exception x) {
x.printStackTrace();
}
return messageBytes;
}
// /////////////////////////////////////////////////////////////////////////////////////
// ///////// ///////////
// ///////// GUI SETUP ///////////
// ///////// ///////////
// /////////////////////////////////////////////////////////////////////////////////////
private void addTreeTable() {
AMFTreeTableModel dataTreeTableModel = generateModel();
treeTable = new JTreeTable(dataTreeTableModel);
tabs.addTab("AMF", new JScrollPane(treeTable));
tabs.setMnemonicAt(0, KeyEvent.VK_1);
}
private String strings(byte[] data) {
StringBuffer sb = new StringBuffer();
boolean foundString = false;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (int i = 0; i < data.length; i++) {
int ch = (char) (data[i] & 0xff);
if (ch >= ' ' && ch < 0x7f) {
baos.write(ch);
foundString = true;
} else if (foundString) {
byte[] tmp = baos.toByteArray();
sb.append(new String(tmp)).append('\n');
baos.reset();
foundString = false;
}
}
return sb.toString();
}
void updateStringsArea() {
if (stringsArea == null) {
stringsArea = new JTextPane();
stringsArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
stringsArea.setEditable(false);
stringsArea.setText(strings(messageBytes));
tabs.addTab("Strings", new JScrollPane(stringsArea));
tabs.setMnemonicAt(1, KeyEvent.VK_2);
} else {
stringsArea.setText(strings(messageBytes));
}
}
void updateHexArea() {
if (hexArea == null) {
fc = new JFileChooser();
exportButton = new JButton("Export");
exportButton.addActionListener(this);
JPanel buttonPanel = new JPanel();
buttonPanel.add(exportButton);
hexArea = new JTextPane();
hexArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
hexArea.setEditable(false);
hexArea.setText(dump("", messageBytes, 0, messageBytes.length));
JPanel hexPanel = new JPanel(new BorderLayout());
hexPanel.add(buttonPanel, BorderLayout.PAGE_END);
hexPanel.add(new JScrollPane(hexArea), BorderLayout.CENTER);
tabs.addTab("HEX", hexPanel);
tabs.setMnemonicAt(2, KeyEvent.VK_3);
} else {
hexArea.setText(dump("", messageBytes, 0, messageBytes.length));
}
}
public void actionPerformed(ActionEvent e) {
try {
if (e.getSource() == exportButton) {
int returnVal = fc.showSaveDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
if (DEBUG)
System.out.print("Exporting data to file "
+ file.getCanonicalPath() + "...");
FileOutputStream fos = new FileOutputStream(file);
fos.write(messageBytes);
fos.close();
if (DEBUG)
System.out.println("done");
}
}
} catch (Exception x) {
x.printStackTrace();
}
}
// /////////////////////////////////////////////////////////////////////////////////////
// ///////// ///////////
// ///////// DATA MODEL ///////////
// ///////// ///////////
// /////////////////////////////////////////////////////////////////////////////////////
private static class AMFData {
private String field;
private String type;
private Class typeClass;
private String value;
private Object object;
private boolean isEditable;
private ActionMessage message;
private static String OBJ_TYPE(Object o) {
if (o == null)
return "Null";
String s = o.getClass().getName();
StringTokenizer token = new StringTokenizer(s, ".");
while (token.hasMoreElements())
s = (String) token.nextElement();
return s;
}
private static String OBJ_VALUE(Object o) {
if (o == null)
return "";
return o.toString();
}
private static Class OBJ_CLASS(Object o) {
if (o == null)
return null;
return o.getClass();
}
public AMFData(String field, String type, String value) {
this.field = field;
this.type = type;
this.typeClass = null;
this.value = value;
this.object = null;
this.isEditable = false;
}
public AMFData(String field, Object object, boolean isEditable,
ActionMessage message) {
try {
this.field = field;
// Call appropriate get method
Object value;
if (object instanceof HashMap) {
value = ((HashMap) object).get(field);
} else {
Method m = object.getClass().getMethod("get" + field, (Class[]) null);
value = m.invoke(object, (Object[]) null);
}
this.type = OBJ_TYPE(value);
this.typeClass = OBJ_CLASS(value);
this.value = OBJ_VALUE(value);
this.object = object;
this.isEditable = isEditable;
this.message = message;
} catch (Exception x) {
x.printStackTrace();
}
}
public String getField() {
return field;
}
public String getType() {
return type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
if (!isEditable)
return;
if (DEBUG)
System.out.println("Setting " + value);
try {
// Invoke appropriate set method
if (object instanceof HashMap) {
((HashMap) object).put(field, value);
} else {
Method m;
// XXX arhhggg, pls fix me...
if (type.equals("Long")) {
m = object.getClass().getMethod("set" + field,
new Class[] { long.class });
m.invoke(object, new Object[] { new Long(value) });
} else {
m = object.getClass().getMethod("set" + field,
new Class[] { typeClass });
m.invoke(object, new Object[] { value });
}
}
// Update instance, this is needed for updating GUI
this.value = value;
encodeAMFMessage(message);
} catch (Exception x) {
x.printStackTrace();
}
}
public String toString() {
return field;
}
}
private static class AMFTreeTableNode extends DefaultMutableTreeNode {
public AMFTreeTableNode(AMFData data) {
super(data);
}
public boolean isEditable(int column) {
return (column == 2) ? true : false;
}
/**
* Called when done editing a cell from {@link DefaultTreeTableModel}.
*/
public void setValueAt(Object value, int column) {
if (DEBUG) {
System.out.println("Setting value at column " + column + " to "
+ value + " (an instance of " + value.getClass() + ")");
}
if (getUserObject() instanceof AMFData) {
AMFData data = (AMFData) getUserObject();
switch (column) {
case 2:
data.setValue(value.toString());
}
}
}
/**
* must override this for setValue from {@link DefaultTreeTableModel} to
* work properly!
*/
public int getColumnCount() {
return 3;
}
/**
* Called when done editing a cell from {@link DefaultTreeTableModel}.
*/
public Object getValueAt(int column) {
if (getUserObject() instanceof AMFData) {
AMFData data = (AMFData) getUserObject();
switch (column) {
case 0:
return data.getField();
case 1:
return data.getType();
case 2:
return data.getValue();
}
}
throw new RuntimeException("Unknown user object: "
+ getUserObject());
}
}
private static class AMFTreeTableModel extends DefaultTreeTableModel {
private static String[] columnNames = { "Field", "Type", "Value" };
public AMFTreeTableModel(TreeNode node) {
super(node);
}
public int getColumnCount() {
return 3;
}
public String getColumnName(int column) {
return columnNames[column];
}
public Class getColumnClass(int column) {
// TODO Auto-generated method stub
return super.getColumnClass(column);
}
public Object getValueAt(Object node, int column) {
AMFTreeTableNode n = (AMFTreeTableNode) node;
return n.getValueAt(column);
}
public boolean isCellEditable(Object node, int column) {
if (column == 0)
return true;
AMFTreeTableNode n = (AMFTreeTableNode) node;
return n.isEditable(column);
}
public void setValueAt(Object value, Object node, int column) {
AMFTreeTableNode n = (AMFTreeTableNode) node;
n.setValueAt(value, column);
}
}
public AMFTreeTableModel generateModel() {
DefaultMutableTreeNode rootNode = new AMFTreeTableNode(new AMFData(
"Message", "", ""));
AMFTreeTableNode headersNode = new AMFTreeTableNode(new AMFData(
"Headers", "", ""));
rootNode.add(headersNode);
for (int i = 0; i < message.getHeaderCount(); i++) {
if (DEBUG)
System.out.println("Reading header: " + i);
AMFTreeTableNode headerNode = new AMFTreeTableNode(new AMFData("["
+ i + "]", "Header Part", ""));
headersNode.add(headerNode);
addObject(headerNode, message.getHeader(i));
}
AMFTreeTableNode bodiesNode = new AMFTreeTableNode(new AMFData(
"Bodies", "", ""));
rootNode.add(bodiesNode);
for (int i = 0; i < message.getBodyCount(); i++) {
if (DEBUG)
System.out.println("Reading body: " + i);
AMFTreeTableNode bodyNode = new AMFTreeTableNode(new AMFData("["
+ i + "]", "Body Part", ""));
bodiesNode.add(bodyNode);
addObject(bodyNode, message.getBody(i));
}
return new AMFTreeTableModel(rootNode);
}
private boolean isComplex(Object object) {
return (object instanceof Object[])
|| (object instanceof HashMap)
|| (object instanceof List)
|| (object != null && object.getClass().getPackage().toString()
.indexOf("flex.messaging.messages") > -1);
}
private void addObject(AMFTreeTableNode node, Object object) {
try {
if (object instanceof Object[]) {
AMFTreeTableNode objectsNode = new AMFTreeTableNode(
new AMFData("", "Array", ""));
node.add(objectsNode);
Object[] array = (Object[]) object;
for (int i = 0; i < array.length; i++)
addObject(objectsNode, array[i]);
} else if (object instanceof HashMap) {
AMFTreeTableNode hashNode = new AMFTreeTableNode(new AMFData(
"", "HashMap", ""));
node.add(hashNode);
HashMap map = (HashMap) object;
for (Iterator it = map.keySet().iterator(); it.hasNext();) {
String key = (String) it.next();
AMFTreeTableNode dataNode = new AMFTreeTableNode(
new AMFData(key, map, true, message));
hashNode.add(dataNode);
}
// } else if (object instanceof List) {
// AMFTreeTableNode listNode =
// new AMFTreeTableNode(new AMFData("", "List", ""));
// node.add(listNode);
//
// List<?> list = (List<?>)object;
// for (Iterator<?> it = list.iterator(); it.hasNext(); ) {
// Object listobj = it.next();
// if (isComplex(listobj))
// addObject(listNode, listobj);
// else {
// String val = (String)it.next();
//
// AMFTreeTableNode dataNode =
// new AMFTreeTableNode(new AMFData("", val, ""));
// listNode.add(dataNode);
// }
// }
} else {
Method[] methods = object.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
String name = m.getName();
Class[] paramTypes = m.getParameterTypes();
if (name.startsWith("get") && !name.equals("getClass")
&& paramTypes.length == 0) {
Object val = m.invoke(object, (Object[]) null);
if (isComplex(val))
addObject(node, val);
else {
String getter = name.substring(3);
AMFData data = new AMFData(getter, object, true,
message);
AMFTreeTableNode objectNode = new AMFTreeTableNode(
data);
node.add(objectNode);
}
}
}
}
} catch (Exception x) {
x.printStackTrace();
}
}
private static final String NL = System.getProperty("line.separator", "\n");
private static boolean isprint(int c) {
return ((c >= 0 && c <= 33) || (c > 126 && c <= 256)) ? false : true;
}
static String dump(String desc, byte[] data, int off, int len) {
final String hex = "0123456789abcdef";
StringBuffer sb = new StringBuffer();
if (desc.length() != 0)
sb.append(desc + NL);
int n = len / 16, i, o;
for (i = 0; i < n; i++) {
o = i * 16;
sb.append(hex.charAt((o >>> 12) & 0x0f)); // offset
sb.append(hex.charAt((o >>> 8) & 0x0f)); // offset
sb.append(hex.charAt((o >>> 4) & 0x0f)); // offset
sb.append(hex.charAt((o) & 0x0f)); // offset
sb.append(": ");
for (int j = 0; j < 16; j++) {
o = data[off + i * 16 + j] & 0xff;
sb.append(hex.charAt((o >>> 4) & 0x0f));
sb.append(hex.charAt((o) & 0x0f));
sb.append(" ");
}
sb.append(" ");
for (int j = 0; j < 16; j++) {
char c = (char) (data[off + i * 16 + j] & 0xff);
sb.append(isprint(c) ? c : '.');
}
sb.append(NL);
}
if ((n = len % 16) != 0) {
o = i * 16;
sb.append(hex.charAt((o >>> 12) & 0x0f)); // offset
sb.append(hex.charAt((o >>> 8) & 0x0f)); // offset
sb.append(hex.charAt((o >>> 4) & 0x0f)); // offset
sb.append(hex.charAt((o) & 0x0f)); // offset
sb.append(": ");
for (int j = 0; j < n; j++) {
o = data[off + i * 16 + j] & 0xff;
sb.append(hex.charAt((o >>> 4) & 0x0f));
sb.append(hex.charAt((o) & 0x0f));
sb.append(" ");
}
for (int j = n; j < 16; j++)
sb.append(" ");
sb.append(" ");
for (int j = 0; j < n; j++) {
char c = (char) (data[off + i * 16 + j] & 0xff);
sb.append(isprint(c) ? c : '.');
}
sb.append(NL);
}
return sb.toString();
}
private static byte[] readfile(String filename) {
byte[] tmp = null;
try {
RandomAccessFile raf = new RandomAccessFile(filename, "r");
tmp = new byte[(int) raf.length()];
raf.readFully(tmp);
raf.close();
} catch (Exception x) {
x.printStackTrace();
}
return tmp;
}
public static void main(String[] args) {
try {
byte[] messageBytes = readfile("c:\\temp\\temp\\amf-passwd-res.bin");
JFrame mainFrame = new JFrame("AMF Panel");
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
AMFPanel amfPanel = new AMFPanel();
amfPanel.setBytes("", messageBytes);
mainFrame.getContentPane().add(amfPanel);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
mainFrame.setSize(screenSize.width * 2 / 3,
screenSize.height * 2 / 3);
mainFrame.setLocationRelativeTo(null);
mainFrame.pack();
mainFrame.setVisible(true);
} catch (Exception x) {
x.printStackTrace();
}
}
}