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.util;
12  
13  import com.jsql.util.LogLevelUtil;
14  import com.jsql.view.swing.terminal.AbstractExploit;
15  import org.apache.commons.lang3.StringUtils;
16  import org.apache.logging.log4j.LogManager;
17  import org.apache.logging.log4j.Logger;
18  
19  import javax.swing.*;
20  import javax.swing.text.BadLocationException;
21  import javax.swing.text.Element;
22  import java.awt.event.InputEvent;
23  import java.awt.event.KeyAdapter;
24  import java.awt.event.KeyEvent;
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.concurrent.atomic.AtomicReference;
28  
29  /**
30   * Keyboard key processing for terminal.
31   */
32  public class KeyAdapterTerminal extends KeyAdapter {
33      
34      private static final Logger LOGGER = LogManager.getRootLogger();
35  
36      /**
37       * Terminal where keys are processed.
38       */
39      private final AbstractExploit terminal;
40  
41      /**
42       * Past commands entered by user.
43       */
44      private final List<String> commandsHistory = new ArrayList<>();
45  
46      /**
47       * Current position in array of past commands.
48       */
49      private int indexCommandsHistory = 0;
50  
51      /**
52       * Create a keyboard processor for a terminal.
53       * @param terminal Terminal where keys are processed
54       */
55      public KeyAdapterTerminal(AbstractExploit terminal) {
56          this.terminal = terminal;
57      }
58  
59      @Override
60      public void keyPressed(KeyEvent keyEvent) {
61          try {
62              var root = this.terminal.getDocument().getDefaultRootElement();
63              int caretPosition = this.terminal.getCaretPosition();
64              int lineNumber = this.terminal.getLineOfOffset(caretPosition);  // Get current line
65  
66              if (this.terminal.getIsEdited().get()) {  // Cancel every user keyboard input if another command has just been sent
67                  keyEvent.consume();
68                  return;
69              }
70      
71              // Get user input
72              var command = new AtomicReference<>(StringUtils.EMPTY);
73              command.set(
74                  this.terminal.getText(
75                      root.getElement(lineNumber).getStartOffset(),
76                      root.getElement(lineNumber).getEndOffset() - root.getElement(lineNumber).getStartOffset()
77                  )
78                  .replace(this.terminal.getPrompt(), StringUtils.EMPTY)
79                  .trim()
80              );
81      
82              if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER) {  // Validate user input ; disable text editing
83                  this.runCommand(keyEvent, command);
84              } else if (keyEvent.getKeyCode() == KeyEvent.VK_UP) {  // Get previous command
85                  this.appendPreviousCommand(keyEvent, root, lineNumber, command);
86              } else if (keyEvent.getKeyCode() == KeyEvent.VK_DOWN) {  // Get next command
87                  this.appendNextCommand(keyEvent, root, lineNumber, command);
88              } else if (keyEvent.getKeyCode() == KeyEvent.VK_LEFT || keyEvent.getKeyCode() == KeyEvent.VK_BACK_SPACE) {  // Go to the left until prompt
89                  this.moveCaretLeft(keyEvent, caretPosition, lineNumber);
90              } else if (keyEvent.getKeyCode() == KeyEvent.VK_HOME) {  // Get to the beginning of the line
91                  this.moveCaretHome(keyEvent, lineNumber);
92              } else if (this.isKeyNotAllowed(keyEvent, caretPosition)) {
93                  keyEvent.consume();
94              } else if (keyEvent.getKeyCode() == KeyEvent.VK_C && (keyEvent.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0) {
95                  this.cancelCommand(keyEvent);
96              }
97          } catch (BadLocationException e) {
98              LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
99          }
100     }
101 
102     private boolean isKeyNotAllowed(KeyEvent keyEvent, int caretPosition) {
103         return
104             // Cancel the select all shortcut Ctrl+A
105             keyEvent.getKeyCode() == KeyEvent.VK_A && (keyEvent.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0
106             // Cancel the *beep* sound if deleting while at the end of line
107             || keyEvent.getKeyCode() == KeyEvent.VK_DELETE && caretPosition == this.terminal.getDocument().getLength()
108             || (keyEvent.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0 && (keyEvent.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0
109             || keyEvent.getKeyCode() == KeyEvent.VK_PAGE_UP
110             || keyEvent.getKeyCode() == KeyEvent.VK_PAGE_DOWN
111             || keyEvent.getKeyCode() == KeyEvent.VK_TAB;
112     }
113 
114     private void cancelCommand(KeyEvent keyEvent) {
115         keyEvent.consume();
116         this.terminal.append("\n");
117         this.terminal.reset();
118     }
119 
120     private void moveCaretHome(KeyEvent keyEvent, int lineNumber) throws BadLocationException {
121         keyEvent.consume();
122         this.terminal.setCaretPosition(this.terminal.getLineStartOffset(lineNumber) + this.terminal.getPrompt().length());
123     }
124 
125     private void moveCaretLeft(KeyEvent keyEvent, int caretPosition, int lineNumber) throws BadLocationException {
126         int newCaretPosition = caretPosition - this.terminal.getLineStartOffset(lineNumber);
127         if (newCaretPosition <= this.terminal.getPrompt().length()) {
128             keyEvent.consume();
129         }
130     }
131 
132     private void appendNextCommand(
133         KeyEvent keyEvent,
134         Element root,
135         int lineNumber,
136         AtomicReference<String> command
137     ) throws BadLocationException {
138         keyEvent.consume();
139    
140         if (this.indexCommandsHistory < this.commandsHistory.size()) {
141             this.indexCommandsHistory++;
142         }
143    
144         if (!this.commandsHistory.isEmpty() && this.indexCommandsHistory < this.commandsHistory.size()) {
145             this.terminal.getDocument().remove(
146                 root.getElement(lineNumber).getStartOffset() + this.terminal.getPrompt().length(),
147                 command.get().length() - 1
148             );
149    
150             this.terminal.append(this.commandsHistory.get(this.indexCommandsHistory));
151             this.terminal.setCaretPosition(this.terminal.getDocument().getLength());
152         }
153     }
154 
155     private void appendPreviousCommand(
156         KeyEvent keyEvent,
157         Element root,
158         int lineNumber,
159         AtomicReference<String> command
160     ) throws BadLocationException {
161         keyEvent.consume();
162    
163         if (this.indexCommandsHistory > 0) {
164             this.indexCommandsHistory--;
165         }
166    
167         if (!this.commandsHistory.isEmpty()) {
168             if (
169                 this.commandsHistory.size() > 1
170                 && this.indexCommandsHistory == this.commandsHistory.size() - 1
171                 && StringUtils.isNotEmpty(command.get())
172             ) {
173                 this.indexCommandsHistory--;
174             }
175    
176             this.terminal.getDocument().remove(
177                 root.getElement(lineNumber).getStartOffset() + this.terminal.getPrompt().length(),
178                 command.get().length() - 1
179             );
180    
181             this.terminal.append(this.commandsHistory.get(this.indexCommandsHistory));
182             this.terminal.setCaretPosition(this.terminal.getDocument().getLength());
183         }
184     }
185 
186     private void runCommand(KeyEvent keyEvent, AtomicReference<String> command) {
187         this.terminal.getIsEdited().set(true);
188         keyEvent.consume();
189         this.terminal.setEditable(false);
190    
191         // Populate cmd list for key up/down
192         if (StringUtils.isNotEmpty(command.get())) {
193             this.commandsHistory.add(command.get());
194             this.indexCommandsHistory = this.commandsHistory.size();
195         }
196 
197         new SwingWorker<>() {  // Thread to give back control to user (SwingUtilities does not)
198             @Override
199             protected Object doInBackground() {
200                 // Inside Swing thread to avoid flickering
201                 Thread.currentThread().setName("SwingWorkerKeyAdapterTerminal");
202 
203                 AbstractExploit terminalCommand = KeyAdapterTerminal.this.terminal;
204                 terminalCommand.append("\n");
205                 
206                 if (StringUtils.isNotEmpty(command.get())) {
207                     terminalCommand.setCaretPosition(terminalCommand.getDocument().getLength());
208                     terminalCommand.action(
209                         command.get(),
210                         terminalCommand.getUuidShell(),
211                         terminalCommand.getUrlShell(),
212                         terminalCommand.getLoginPassword()
213                     );
214                 } else {
215                     terminalCommand.reset();
216                 }
217                 return null;
218             }
219         }.execute();
220     }
221 }