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