View Javadoc
1   /*******************************************************************************
2    * Copyhacked (H) 2012-2025.
3    * This program and the accompanying materials
4    * are made available under no term at all, use it like
5    * you want, but share and discuss it
6    * every time possible with every body.
7    * 
8    * Contributors:
9    *      ron190 at ymail dot com - initial implementation
10   ******************************************************************************/
11  package com.jsql.view.swing.terminal;
12  
13  import com.jsql.util.LogLevelUtil;
14  import com.jsql.view.swing.terminal.util.BlockCaret;
15  import com.jsql.view.swing.terminal.util.KeyAdapterTerminal;
16  import com.jsql.view.swing.util.MediatorHelper;
17  import com.jsql.view.swing.util.UiUtil;
18  import org.apache.commons.lang3.StringUtils;
19  import org.apache.logging.log4j.LogManager;
20  import org.apache.logging.log4j.Logger;
21  
22  import javax.swing.*;
23  import javax.swing.text.BadLocationException;
24  import javax.swing.text.Style;
25  import javax.swing.text.StyleConstants;
26  import java.awt.*;
27  import java.awt.event.MouseMotionListener;
28  import java.net.MalformedURLException;
29  import java.net.URI;
30  import java.net.URISyntaxException;
31  import java.net.URL;
32  import java.util.UUID;
33  import java.util.concurrent.atomic.AtomicBoolean;
34  
35  /**
36   * A Terminal completely built from swing text pane.
37   */
38  public abstract class AbstractExploit extends JTextPane {
39      
40      /**
41       * Log4j logger sent to view.
42       */
43      private static final Logger LOGGER = LogManager.getRootLogger();
44  
45      /**
46       * True if terminal is processing command.
47       */
48      private final AtomicBoolean isEdited = new AtomicBoolean(false);
49  
50      /**
51       * Server name or IP to display on prompt.
52       */
53      private final String host;
54  
55      /**
56       * User and password for database.
57       */
58      private String[] loginPassword = null;
59      private final UUID uuidShell;
60      private final String urlShell;
61  
62      /**
63       * Style used for coloring text.
64       */
65      private final transient Style style = this.addStyle("Necrophagist's next album is 2014.", null);
66  
67      /**
68       * Length of prompt.
69       */
70      private String prompt = StringUtils.EMPTY;
71  
72      /**
73       * Text to display next caret.
74       */
75      private final String labelShell;
76      
77      /**
78       * Build a shell instance.
79       * @param uuidShell Unique identifier to discriminate beyond multiple opened terminals
80       * @param urlShell URL of current shell
81       * @param labelShell Type of shell to display on prompt
82       */
83      protected AbstractExploit(UUID uuidShell, String urlShell, String labelShell) throws MalformedURLException, URISyntaxException {
84          this(uuidShell, urlShell, labelShell, true);
85      }
86      protected AbstractExploit(UUID uuidShell, String urlShell, String labelShell, boolean isAddingPrompt) throws MalformedURLException, URISyntaxException {
87          this.uuidShell = uuidShell;
88          this.urlShell = urlShell;
89          this.labelShell = labelShell;
90  
91          URL url;
92          if (StringUtils.isEmpty(urlShell)) {  // udf
93              url = new URI(MediatorHelper.model().getMediatorUtils().getConnectionUtil().getUrlByUser()).toURL();
94          } else {
95              url = new URI(urlShell).toURL();
96          }
97          this.host = url.getHost();
98  
99          this.setFont(new Font(UiUtil.FONT_NAME_MONO_NON_ASIAN, Font.PLAIN, UIManager.getFont("TextArea.font").getSize()));
100         this.setCaret(new BlockCaret());
101         this.setBackground(Color.BLACK);
102         this.setForeground(Color.LIGHT_GRAY);
103 
104         if (isAddingPrompt) {
105             this.displayPrompt(true);
106         } else {
107             this.append("Waiting for reverse connection...\n");
108         }
109 
110         this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
111         this.setTransferHandler(null);
112         this.setHighlighter(null);
113 
114         this.addMouseListener(new EmptyFocusCopy(this));
115         this.addKeyListener(new KeyAdapterTerminal(this));
116     }
117 
118     /**
119      * Run when cmd is validated.
120      * @param cmd Command to execute
121      * @param terminalID Unique ID for terminal instance
122      * @param wbhPath URL of shell
123      * @param arg Additional parameters (User and password for SQLShell)
124      */
125     public abstract void action(String cmd, UUID terminalID, String wbhPath, String... arg);
126     
127     /**
128      * Update terminal and use default behavior.
129      */
130     public void reset() {
131         this.reset(true);
132     }
133     public void reset(boolean isPromptVisible) {
134         this.isEdited.set(false);
135         this.setEditable(true);
136         if (isPromptVisible) {
137             this.displayPrompt(false);
138         }
139         this.setCaretPosition(this.getDocument().getLength());
140         this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
141     }
142 
143     /**
144      * Add a text at the end of textpane.
145      * @param string Text to add
146      */
147     public void append(String string) {
148         try {
149             var doc = this.getDocument();
150             doc.insertString(doc.getLength(), string, null);
151         } catch (BadLocationException e) {
152             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
153         }
154     }
155     
156     /**
157      * Append prompt to textpane, measure prompt the first time is used.
158      * @param isAddingPrompt Should we measure prompt length?
159      */
160     public void displayPrompt(boolean isAddingPrompt) {
161         StyleConstants.setUnderline(this.style, true);
162         this.appendPrompt("jsql", Color.LIGHT_GRAY, isAddingPrompt);
163         StyleConstants.setUnderline(this.style, false);
164 
165         this.appendPrompt(StringUtils.SPACE + this.labelShell, Color.LIGHT_GRAY, isAddingPrompt);
166         this.appendPrompt("[", new Color(0x32BF32), isAddingPrompt);
167         this.appendPrompt(this.host, new Color(0xBFBF19), isAddingPrompt);
168         this.appendPrompt("]", new Color(0x32BF32), isAddingPrompt);
169         this.appendPrompt(" >", new Color(0xBF6464), isAddingPrompt);
170         this.appendPrompt(StringUtils.SPACE, Color.LIGHT_GRAY, isAddingPrompt);
171     }
172 
173     /**
174      * Add a colored string to the textpane, measure prompt at the same time.
175      * @param string Text to append
176      * @param color Color of text
177      * @param isAddingPrompt Should we measure prompt length?
178      */
179     private void appendPrompt(String string, Color color, boolean isAddingPrompt) {
180         try {
181             StyleConstants.setForeground(this.style, color);
182             this.getStyledDocument().insertString(this.getStyledDocument().getLength(), string, this.style);
183             if (isAddingPrompt) {
184                 this.prompt += string;
185             }
186         } catch (BadLocationException e) {
187             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
188         }
189     }
190 
191     /**
192      * NoWrap.
193      */
194     @Override
195     public boolean getScrollableTracksViewportWidth() {
196         return this.getUI().getPreferredSize(this).width <= this.getParent().getSize().width;
197     }
198 
199     /**
200      * Cancel every mouse movement processing like drag/drop.
201      */
202     @Override
203     public synchronized void addMouseMotionListener(MouseMotionListener l) {
204         // Do nothing
205     }
206 
207     /**
208      * Get index of line for current offset (generally cursor position).
209      * @param offset Position on the line
210      * @return Index of the line
211      */
212     public int getLineOfOffset(int offset) throws BadLocationException {
213         var errorMsg = "Can't translate offset to line";
214         var doc = this.getDocument();
215         
216         if (offset < 0) {
217             throw new BadLocationException(errorMsg, -1);
218         } else if (offset > doc.getLength()) {
219             throw new BadLocationException(errorMsg, doc.getLength() + 1);
220         } else {
221             var map = doc.getDefaultRootElement();
222             return map.getElementIndex(offset);
223         }
224     }
225 
226     /**
227      * Get position of the beginning of the line.
228      * @param line Index of the line
229      * @return Offset of line
230      */
231     public int getLineStartOffset(int line) throws BadLocationException {
232         var map = this.getDocument().getDefaultRootElement();
233         
234         if (line < 0) {
235             throw new BadLocationException("Negative line", -1);
236         } else if (line >= map.getElementCount()) {
237             throw new BadLocationException("No such line", this.getDocument().getLength() + 1);
238         } else {
239             var lineElem = map.getElement(line);
240             return lineElem.getStartOffset();
241         }
242     }
243 
244     
245     // Getter and setter
246     
247     public AtomicBoolean getIsEdited() {
248         return this.isEdited;
249     }
250 
251     public UUID getUuidShell() {
252         return this.uuidShell;
253     }
254 
255     public String getUrlShell() {
256         return this.urlShell;
257     }
258 
259     public String getPrompt() {
260         return this.prompt;
261     }
262 
263     public String[] getLoginPassword() {
264         return this.loginPassword;
265     }
266 
267     public void setLoginPassword(String[] loginPassword) {
268         this.loginPassword = loginPassword;
269     }
270 }