package org.racsor.jmeter.flex.messaging.util;
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.Map;
import java.util.StringTokenizer;
import javax.swing.JButton;
import javax.swing.JEditorPane;
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.text.ComponentView;
import javax.swing.text.EditorKit;
import javax.swing.text.Element;
import javax.swing.text.StyleConstants;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import org.racsor.jmeter.flex.messaging.swing.JTreeTable;
import org.racsor.jmeter.flex.messaging.swing.treetable.DefaultTreeTableModel;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.amf.ASObject;
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 ActionListener {
private static final String X_AMF = "application/x-amf"; // $NON-NLS-1$
// Keep copies of the two editors needed
private static final EditorKit customisedEditor = new LocalHTMLEditorKit();
private static final EditorKit defaultHtmlEditor = JEditorPane.createEditorKitForContentType(X_AMF);
/** 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 xmlArea;
private JTextPane hexArea;
private JButton exportButton;
private JFileChooser fc;
public static boolean DEBUG = false;
public UtilsFlexMessage utilsFlex = new UtilsFlexMessage();
/** 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();
AmfTrace trace = new AmfTrace();
trace = parseAMFMessage();
addTreeTable();
updateXmlArea();
updateStringsArea(trace);
updateHexArea();
add(tabs);
}
public byte[] getBytes() {
encodeAMFMessage();
return (byte[]) messageBytes.clone();
}
private AmfTrace parseAMFMessage() {
AmfTrace trace = null;
try {
serialContext = SerializationContext.getSerializationContext();
serialContext.instantiateTypes = false;
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();
}
return trace;
}
private void encodeAMFMessage() {
try {
AmfTrace trace = null;
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;
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);
treeTable.setShowGrid(false);
// treeTable.setAutoResizeMode(JTreeTable.AUTO_RESIZE_ALL_COLUMNS);
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();
}
private void updateXmlArea() {
if (xmlArea == null) {
fc = new JFileChooser();
exportButton = new JButton("Export");
exportButton.addActionListener(this);
JPanel buttonPanel = new JPanel();
buttonPanel.add(exportButton);
xmlArea = new JTextPane();
// stringsArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
xmlArea.setEditable(true);
JPanel xmlPanel = new JPanel(new BorderLayout());
xmlPanel.add(buttonPanel, BorderLayout.PAGE_END);
xmlPanel.add(new JScrollPane(xmlArea), BorderLayout.CENTER);
tabs.addTab("XML", new JScrollPane(xmlPanel));
tabs.setMnemonicAt(1, KeyEvent.VK_2);
}
xmlArea.setText(messageToXML());
}
private String messageToXML() {
utilsFlex.parseInputStream(new ByteArrayInputStream(messageBytes));
String message = utilsFlex.messageToXML();
return message;
}
void updateStringsArea(AmfTrace trace) {
if (stringsArea == null) {
stringsArea = new JTextPane();
// stringsArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
stringsArea.setEditorKitForContentType(X_AMF, customisedEditor);
stringsArea.setContentType(X_AMF);
stringsArea.getDocument().putProperty("IgnoreCharsetDirective", Boolean.TRUE); // $NON-NLS-1$
stringsArea.setContentType("text/txt");
stringsArea.setEditable(false);
tabs.addTab("Strings", new JScrollPane(stringsArea));
tabs.setMnemonicAt(2, KeyEvent.VK_3);
}
stringsArea.setText(trace.toString());
}
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", hexArea);
tabs.setMnemonicAt(3, KeyEvent.VK_4);
} 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();
utilsFlex.serializeMessage(file.getCanonicalPath(), xmlArea.getText());
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, ActionMessage message) {
this.field = field;
this.type = type;
this.typeClass = null;
this.value = value;
this.object = null;
this.isEditable = false;
this.message = message;
}
public AMFData(String field, Object object, Object value, boolean isEditable, ActionMessage message) {
this.field = field;
this.type = object.getClass().getName();
this.typeClass = null;
this.value = OBJ_VALUE(value);
this.object = object;
this.isEditable = isEditable;
this.message = message;
}
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, null);
value = m.invoke(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 if (object instanceof String) {
this.value = 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) {
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", "", "", message));
AMFTreeTableNode headersNode = new AMFTreeTableNode(new AMFData("Headers", "", "", message));
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", "", message));
headersNode.add(headerNode);
addObject(headerNode, message.getHeader(i));
}
AMFTreeTableNode bodiesNode = new AMFTreeTableNode(new AMFData("Bodies", "", "", message));
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", "", message));
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().contains("flex.messaging.messages"));
}
private void addObject(AMFTreeTableNode node, Object object) {
try {
if (object instanceof String || object instanceof Integer || object instanceof Long) {
AMFTreeTableNode hashNode = new AMFTreeTableNode(new AMFData("", object.getClass().getName(), object.toString(), message));
node.add(hashNode);
} else if (object instanceof Object[]) {
AMFTreeTableNode objectsNode = new AMFTreeTableNode(new AMFData("", "Object[]", "", message));
node.add(objectsNode);
Object[] array = (Object[]) object;
for (int i = 0; i < array.length; i++)
addObject(objectsNode, array[i]);
} else if (object instanceof ASObject) {
if (DEBUG)
System.out.println(((ASObject) object).getType());
AMFTreeTableNode objectsNode = new AMFTreeTableNode(new AMFData("", ((ASObject) object).getType(), "", message));
node.add(objectsNode);
ASObject o = ((ASObject) object);
Iterator it = o.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pairs = (Map.Entry) it.next();
Object getValue = pairs.getValue();
if (getValue instanceof Object[]) {
Object[] array = (Object[]) getValue;
for (int i = 0; i < array.length; i++)
addObject(objectsNode, array[i]);
} else if (getValue instanceof ASObject) {
addObject(objectsNode, getValue);
} else {
if (DEBUG)
System.out.println(pairs.getKey() + " = " + pairs.getValue());
AMFData data = new AMFData((String) pairs.getKey(), pairs.getKey(), pairs.getValue(), false, message);
AMFTreeTableNode objectNode = new AMFTreeTableNode(data);
node.add(objectNode);
}
}
} else if (object instanceof HashMap) {
AMFTreeTableNode hashNode = new AMFTreeTableNode(new AMFData("", "HashMap", "", message));
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, false, 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) {
try {
Object val = m.invoke(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 e) {
AMFData data = new AMFData(name, "String", "value", message);
AMFTreeTableNode objectNode = new AMFTreeTableNode(data);
node.add(objectNode);
e.printStackTrace();
}
}
}
}
} 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;
}
private static class LocalHTMLEditorKit extends HTMLEditorKit {
private static final long serialVersionUID = -3399554318202905392L;
private static final ViewFactory defaultFactory = new LocalHTMLFactory();
@Override
public ViewFactory getViewFactory() {
return defaultFactory;
}
private static class LocalHTMLFactory extends javax.swing.text.html.HTMLEditorKit.HTMLFactory {
/*
* Provide dummy implementations to suppress download and display of
* related resources: - FRAMEs - IMAGEs TODO create better dummy
* displays TODO suppress LINK somehow
*/
@Override
public View create(Element elem) {
Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
if (o instanceof HTML.Tag) {
HTML.Tag kind = (HTML.Tag) o;
if (kind == HTML.Tag.FRAME) {
return new ComponentView(elem);
} else if (kind == HTML.Tag.IMG) {
return new ComponentView(elem);
}
}
return super.create(elem);
}
}
}
public static void main(String[] args) {
try {
// byte[] messageBytes = readfile("C:\\TIC_LOCAL\\EclipseProjects\\OpenSourceRacsor\\cc_request\\jmeter\\response_1.x-amf");
// byte[] messageBytes = readfile("C:\\TIC_LOCAL\\EclipseProjects\\OpenSourceRacsor\\cc_request\\jmeter\\bosses_attack.binary");
byte[] messageBytes = readfile("C:\\Archivos de programa\\jakarta-jmeter-2.5.1\\bin\\post1.x-amf");
// byte[] messageBytes = readfile("C:\\TIC_LOCAL\\EclipseProjects\\OpenSourceRacsor\\cc_request\\jmeter\\attackbossSisterSnakeMosheRuiz.binary");
// byte[] messageBytes = readfile("C:\\TIC_LOCAL\\EclipseProjects\\OpenSourceRacsor\\jmeter-amf-deserialize\\src\\test\\resources\\serialize\\post1.x-amf");
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();
}
}
}