/*
Copyright (C) 2009 Rachel Engel
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 2
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
/*
* ThirdIdea.java
*
* Created on May 14, 2009, 3:30:45 PM
*/
package com.isecpartners.gizmo;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import org.apache.commons.codec.binary.Base64;
import org.jdesktop.application.Action;
/**
*
* @author rachel
*/
public class FourthIdea extends javax.swing.JFrame implements CaretListener {
private Properties command_translation;
private BlobScroller scroller;
private String reqText;
private List<String> temp_files_to_clean_up = new LinkedList<String>();
private File buffer_file;
private static final String command_map_file_name = "command_map.properties";
/*
* I think the way to keep text selected after the user's clicked on the command area is to
* re-highlight any text when the command line gets focus. If we can't detect when the command
* line gets focus, we re-highlight it when we execute the command. Once the command is finished,
* we re-highlight the new text and start waiting for a click or a keypress. Either a click or a
* keypress de-selects the text. I think. */
public FourthIdea(final BlobScroller scroller, final HTTPMessage msg, final boolean request) {
try {
this.buffer_file = File.createTempFile("tmp", "end");
} catch (IOException ex) {
Logger.getLogger(FourthIdea.class.getName()).log(Level.SEVERE, null, ex);
}
initComponents();
init(scroller, msg.contents(), new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (request) {
scroller.setCommand(jTextPane1.getText(), request, (HttpRequest) msg);
}
FourthIdea.this.dispose();
}
});
}
public FourthIdea(final BlobScroller scroller, String str) {
initComponents();
init(scroller, str, new AbstractAction() {
public void actionPerformed(ActionEvent e) {
FourthIdea.this.dispose();
}
});
}
private String apply_macros(String command_string) {
if (command_translation == null)
return command_string;
for (Object obj : command_translation.keySet()) {
String key = (String)obj;
String value = (String) command_translation.get(key);
String regex = "^([\\s]*\\" + key + ").*";
// so the goal here is to substitute the pattern *only* if it shows up first
// and ignore trailing spaces. i want to take the first group, remove that
// and then prepend the replacement string to what's left.
Matcher match = Pattern.compile(regex).matcher(command_string);
if (match.matches()) {
if (match.groupCount() != 1)
break;
String found_str = match.group(1);
int end_of_match = command_string.indexOf(found_str) + found_str.length();
command_string = value + " " + command_string.substring(end_of_match + 1);
}
command_string = Pattern.compile(key).matcher(command_string).replaceAll(value);
}
return command_string;
}
private void init(final BlobScroller scroller, String str, final AbstractAction action) {
this.scroller = scroller;
this.reqText = str;
if (System.getProperty("os.name").toUpperCase().contains("WINDOWS")) {
defaultShellField.setText("cmd.exe /c");
} else {
defaultShellField.setText("sh -c");
}
this.addWindowListener(
new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e) {action.actionPerformed(null);
}
});
command_translation = new Properties();
try {
command_translation.load(new FileInputStream(command_map_file_name));
} catch (IOException ex) {
try {
FileOutputStream fout = new FileOutputStream(command_map_file_name);
fout.write("".getBytes());
fout.close();
} catch (IOException ex1) {}
GizmoView.log("couldn't find " + command_map_file_name + ".. turning off macro translation");
}
jTextPane1.setText(str);
jTextPane1.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "esc");
jTextPane1.getActionMap().put("esc", action);
}
// Turn an environment map into an environment vector.
// Get an environment into a vector. Save the first n slots for use.
private String[] getenv(int n) {
Map<String, String> m = System.getenv();
String ret[] = new String[n + m.size()];
for (Map.Entry<String, String> e : m.entrySet()) {
if (e.getKey().toUpperCase().trim().equals("PATH")) {
ret[n++] = e.getKey() + "=" + e.getValue() + System.getProperty("path.separator") + "scripts";
} else {
ret[n++] = e.getKey() + "=" + e.getValue();
}
}
return ret;
}
private void cleanup_temp_files() {
for (String str : temp_files_to_clean_up) {
new File(str).delete();
}
}
private void executeCommand(final String text) {
final InputStream procInput;
new Thread(new Runnable() {
public void run() {
String newRequestText = "";
try {
temp_files_to_clean_up.add(buffer_file.getCanonicalPath());
PrintWriter out = new PrintWriter(new FileOutputStream(buffer_file));
byte[] requestBuf = null;
if (jTextPane1.getSelectedText() != null &&
jTextPane1.getSelectedText().length() > 0) {
out.write(jTextPane1.getSelectedText());
requestBuf = jTextPane1.getSelectedText().getBytes();
} else {
out.write(jTextPane1.getText());
requestBuf = jTextPane1.getText().getBytes();
}
out.close();
String env[] = getenv(1);
env[0] = "BUF=" + buffer_file.getCanonicalPath();
String cmd = "";
if (System.getProperty("os.name").toUpperCase().contains("WINDOWS")) {
cmd = "type " + buffer_file + "|" + text;
} else {
cmd = "cat " + buffer_file + "|" + text;
}
newRequestText = exec(cmd, env, requestBuf);
} catch (IOException ex) {
Logger.getLogger(FourthIdea.class.getName()).log(Level.SEVERE, null, ex);
} finally {
if (jTextPane1.getSelectedText() != null && jTextPane1.getSelectedText().length() > 0) {
jTextPane1.replaceSelection(newRequestText);
} else {
FourthIdea.this.jTextPane1.setText(newRequestText);
}
}
}
private String readWholeFile(File file) {
InputStream in = null;
String ret = "";
try {
in = new FileInputStream(file);
int file_size = Integer.MAX_VALUE;
if (file.length() < Integer.MAX_VALUE) {
file_size = (int) file.length();
}
byte[] buf = new byte[file_size];
// truncated because for some reason, a byte array is initialized with an int instead of a long
// so this's a bit awful, but if you're editing a request bigger than 2G in size, then don't
in.read(buf);
ret = new String(buf);
} catch (IOException ex) {
Logger.getLogger(FourthIdea.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
in.close();
} catch (IOException ex) {
Logger.getLogger(FourthIdea.class.getName()).log(Level.SEVERE, null, ex);
}
}
return ret;
}
}).start();
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
jLabel1 = new javax.swing.JLabel();
defaultShellField = new javax.swing.JTextField();
jScrollPane1 = new javax.swing.JScrollPane();
jTextPane1 = new javax.swing.JTextPane();
jTextField1 = new javax.swing.JTextField();
jPanel1 = new javax.swing.JPanel();
jPanel2 = new javax.swing.JPanel();
jScrollPane2 = new javax.swing.JScrollPane();
shell_output_area = new javax.swing.JTextArea();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setName("Form"); // NOI18N
org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance().getContext().getResourceMap(FourthIdea.class);
jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N
jLabel1.setName("jLabel1"); // NOI18N
defaultShellField.setText(resourceMap.getString("defaultShellField.text")); // NOI18N
defaultShellField.setName("defaultShellField"); // NOI18N
jScrollPane1.setName("jScrollPane1"); // NOI18N
jTextPane1.setName("jTextPane1"); // NOI18N
jScrollPane1.setViewportView(jTextPane1);
jTextField1.setText(resourceMap.getString("jTextField1.text")); // NOI18N
jTextField1.setName("jTextField1"); // NOI18N
jTextField1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jTextField1ActionPerformed(evt);
}
});
jPanel1.setName("jPanel1"); // NOI18N
jPanel2.setName("jPanel2"); // NOI18N
jScrollPane2.setName("jScrollPane2"); // NOI18N
shell_output_area.setBackground(resourceMap.getColor("shell_output_area.background")); // NOI18N
shell_output_area.setColumns(20);
shell_output_area.setEditable(false);
shell_output_area.setRows(5);
shell_output_area.setName("shell_output_area"); // NOI18N
jScrollPane2.setViewportView(shell_output_area);
javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
jPanel2.setLayout(jPanel2Layout);
jPanel2Layout.setHorizontalGroup(
jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 775, Short.MAX_VALUE)
);
jPanel2Layout.setVerticalGroup(
jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jScrollPane2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 295, Short.MAX_VALUE)
);
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jPanel2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
jPanel1Layout.setVerticalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jPanel2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(defaultShellField, javax.swing.GroupLayout.PREFERRED_SIZE, 81, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(628, 628, 628))
.addComponent(jTextField1, javax.swing.GroupLayout.DEFAULT_SIZE, 775, Short.MAX_VALUE)
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 775, Short.MAX_VALUE)
.addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, 19, Short.MAX_VALUE)
.addComponent(defaultShellField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 305, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
pack();
}// </editor-fold>//GEN-END:initComponents
private void jTextField1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jTextField1ActionPerformed
executeCommand(jTextField1.getText());
}//GEN-LAST:event_jTextField1ActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTextField defaultShellField;
private javax.swing.JLabel jLabel1;
private javax.swing.JPanel jPanel1;
private javax.swing.JPanel jPanel2;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JScrollPane jScrollPane2;
private javax.swing.JTextField jTextField1;
private javax.swing.JTextPane jTextPane1;
private javax.swing.JTextArea shell_output_area;
// End of variables declaration//GEN-END:variables
public String exec(String command_string, String[] env, byte[] requestBuf) {
StringBuffer display_text = new StringBuffer();
String out = "";
try {
String shell = defaultShellField.getText();
Process proc = null;
command_string = apply_macros(command_string);
if (System.getProperty("os.name").toUpperCase().contains("WINDOWS")) {
String[] args = translateCommandline(shell + " " + command_string);
proc = Runtime.getRuntime().exec(args, env);
} else {
String[] shell_pieces = shell.split("\\s+");
String args[] = new String[shell_pieces.length + 1];
System.arraycopy(shell_pieces, 0, args, 0, shell_pieces.length);
args[args.length - 1] = command_string;
proc = Runtime.getRuntime().exec(args, env);
}
out = readOutput(proc);
String err = readError(proc);
proc.waitFor();
proc.destroy();
display_text.append("\n\n" + out + err);
appendToOutputArea(display_text.toString());
} catch (InterruptedException ex) {
Logger.getLogger(FourthIdea.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(FourthIdea.class.getName()).log(Level.SEVERE, null, ex);
} catch (Exception e) {
GizmoView.log(e.toString());
}
return out;
}
private void appendToOutputArea(String output) {
shell_output_area.setText(shell_output_area.getText() + output);
shell_output_area.getCaret().setVisible(true);
shell_output_area.setCaretPosition(shell_output_area.getText().length() - 1);
}
private void pipe(String command_string) {
command_string = command_string.trim().replaceFirst("|", "");
}
private String readError(Process proc) {
return readStream(proc, proc.getErrorStream());
}
private String readOutput(Process proc) {
return readStream(proc, proc.getInputStream());
}
private static String[] translateCommandline(final String toProcess) {
if (toProcess == null || toProcess.length() == 0) {
// no command? no string
return new String[0];
}
// parse with a simple finite state machine
final int normal = 0;
final int inQuote = 1;
final int inDoubleQuote = 2;
int state = normal;
StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
Vector v = new Vector();
StringBuffer current = new StringBuffer();
boolean lastTokenHasBeenQuoted = false;
while (tok.hasMoreTokens()) {
String nextTok = tok.nextToken();
switch (state) {
case inQuote:
if ("\'".equals(nextTok)) {
lastTokenHasBeenQuoted = true;
state = normal;
} else {
current.append(nextTok);
}
break;
case inDoubleQuote:
if ("\"".equals(nextTok)) {
lastTokenHasBeenQuoted = true;
state = normal;
} else {
current.append(nextTok);
}
break;
default:
if ("\'".equals(nextTok)) {
state = inQuote;
} else if ("\"".equals(nextTok)) {
state = inDoubleQuote;
} else if (" ".equals(nextTok)) {
if (lastTokenHasBeenQuoted || current.length() != 0) {
v.addElement(current.toString());
current = new StringBuffer();
}
} else {
current.append(nextTok);
}
lastTokenHasBeenQuoted = false;
break;
}
}
if (lastTokenHasBeenQuoted || current.length() != 0) {
v.addElement(current.toString());
}
if (state == inQuote || state == inDoubleQuote) {
throw new IllegalArgumentException("Unbalanced quotes in " + toProcess);
}
String[] args = new String[v.size()];
v.copyInto(args);
return args;
}
private String readStream(Process proc, InputStream in) {
StringBuffer ret = new StringBuffer();
try {
int n = in.read();
while (n != -1) {
ret.append((char) n);
n = in.read();
}
} catch (IOException ex) {
Logger.getLogger(FourthIdea.class.getName()).log(Level.SEVERE, null, ex);
}
return ret.toString();
}
private String readWholeFile(String filename) {
InputStream in = null;
String ret = "";
try {
File f = new File(filename);
in = new FileInputStream(f);
int file_size = Integer.MAX_VALUE;
if (f.length() < Integer.MAX_VALUE) {
file_size = (int) f.length();
}
byte[] buf = new byte[file_size];
// truncated because for some reason, a byte array is initialized with an int instead of a long
// so this's a bit awful, but if you're editing a request bigger than 2G in size, then don't
in.read(buf);
ret = new String(buf);
} catch (IOException ex) {
Logger.getLogger(FourthIdea.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
in.close();
} catch (IOException ex) {
Logger.getLogger(FourthIdea.class.getName()).log(Level.SEVERE, null, ex);
}
}
return ret;
}
private String process_req_variable(String text) {
PrintWriter out = null;
String ret = text;
try {
File tmp2 = File.createTempFile("tmp", "end");
temp_files_to_clean_up.add(tmp2.getCanonicalPath());
out = new PrintWriter(new FileOutputStream(tmp2));
out.write(reqText);
out.close();
ret = ret.replace("$REQ", "\"" + tmp2.getCanonicalPath() + "\"");
} catch (IOException ex) {
Logger.getLogger(FourthIdea.class.getName()).log(Level.SEVERE, null, ex);
} finally {
out.close();
}
return ret;
}
@Action
public void b64encode() {
String original_text = jTextPane1.getSelectedText();
String new_text = new String(Base64.encodeBase64(original_text.getBytes()));
jTextPane1.replaceSelection(new_text);
}
@Action
public void b64decode() {
String original_text = jTextPane1.getSelectedText();
String new_text = new String(Base64.decodeBase64(original_text.getBytes()));
jTextPane1.replaceSelection(new_text);
}
@Action
public void urlencode() {
try {
String original_text = jTextPane1.getSelectedText();
String new_text = java.net.URLEncoder.encode(original_text, "UTF-8");
jTextPane1.replaceSelection(new_text);
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(FourthIdea.class.getName()).log(Level.SEVERE, null, ex);
}
}
@Action
public void urldecode() {
try {
String original_text = jTextPane1.getSelectedText();
String new_text = java.net.URLDecoder.decode(original_text, "UTF-8");
jTextPane1.replaceSelection(new_text);
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(FourthIdea.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void caretUpdate(CaretEvent e) {
throw new UnsupportedOperationException("Not supported yet.");
}
private void writeToProcess(Process proc, byte[] buf) {
try {
proc.getOutputStream().write(buf, 0, buf.length);
} catch (IOException ex) {
Logger.getLogger(FourthIdea.class.getName()).log(Level.SEVERE, null, ex);
}
}
}