| 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 |
1
1. <init> : negated conditional → NO_COVERAGE |
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 |
1
1. <init> : removed call to com/jsql/view/swing/terminal/AbstractExploit::setFont → NO_COVERAGE |
this.setFont(new Font(UiUtil.FONT_NAME_MONO_NON_ASIAN, Font.PLAIN, UIManager.getFont("TextArea.font").getSize())); |
| 97 |
1
1. <init> : removed call to com/jsql/view/swing/terminal/AbstractExploit::setCaret → NO_COVERAGE |
this.setCaret(new BlockCaret()); |
| 98 |
1
1. <init> : removed call to com/jsql/view/swing/terminal/AbstractExploit::setBackground → NO_COVERAGE |
this.setBackground(Color.BLACK); |
| 99 |
1
1. <init> : removed call to com/jsql/view/swing/terminal/AbstractExploit::setForeground → NO_COVERAGE |
this.setForeground(Color.LIGHT_GRAY); |
| 100 | ||
| 101 |
1
1. <init> : negated conditional → NO_COVERAGE |
if (isAddingPrompt) { |
| 102 |
1
1. <init> : removed call to com/jsql/view/swing/terminal/AbstractExploit::displayPrompt → NO_COVERAGE |
this.displayPrompt(true); |
| 103 | } else { | |
| 104 |
1
1. <init> : removed call to com/jsql/view/swing/terminal/AbstractExploit::append → NO_COVERAGE |
this.append("Waiting for reverse connection...\n"); |
| 105 | } | |
| 106 | ||
| 107 |
1
1. <init> : removed call to com/jsql/view/swing/terminal/AbstractExploit::setCursor → NO_COVERAGE |
this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); |
| 108 |
1
1. <init> : removed call to com/jsql/view/swing/terminal/AbstractExploit::setTransferHandler → NO_COVERAGE |
this.setTransferHandler(null); |
| 109 |
1
1. <init> : removed call to com/jsql/view/swing/terminal/AbstractExploit::setHighlighter → NO_COVERAGE |
this.setHighlighter(null); |
| 110 | ||
| 111 |
1
1. <init> : removed call to com/jsql/view/swing/terminal/AbstractExploit::addMouseListener → NO_COVERAGE |
this.addMouseListener(new EmptyFocusCopy(this)); |
| 112 |
1
1. <init> : removed call to com/jsql/view/swing/terminal/AbstractExploit::addKeyListener → NO_COVERAGE |
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 |
1
1. reset : removed call to com/jsql/view/swing/terminal/AbstractExploit::reset → NO_COVERAGE |
this.reset(true); |
| 129 | } | |
| 130 | public void reset(boolean isPromptVisible) { | |
| 131 |
1
1. reset : removed call to java/util/concurrent/atomic/AtomicBoolean::set → NO_COVERAGE |
this.isEdited.set(false); |
| 132 |
1
1. reset : removed call to com/jsql/view/swing/terminal/AbstractExploit::setEditable → NO_COVERAGE |
this.setEditable(true); |
| 133 |
1
1. reset : negated conditional → NO_COVERAGE |
if (isPromptVisible) { |
| 134 |
1
1. reset : removed call to com/jsql/view/swing/terminal/AbstractExploit::displayPrompt → NO_COVERAGE |
this.displayPrompt(false); |
| 135 | } | |
| 136 |
1
1. reset : removed call to com/jsql/view/swing/terminal/AbstractExploit::setCaretPosition → NO_COVERAGE |
this.setCaretPosition(this.getDocument().getLength()); |
| 137 |
1
1. reset : removed call to com/jsql/view/swing/terminal/AbstractExploit::setCursor → NO_COVERAGE |
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 |
1
1. append : removed call to javax/swing/text/Document::insertString → NO_COVERAGE |
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 |
1
1. displayPrompt : removed call to javax/swing/text/StyleConstants::setUnderline → NO_COVERAGE |
StyleConstants.setUnderline(this.style, true); |
| 159 |
1
1. displayPrompt : removed call to com/jsql/view/swing/terminal/AbstractExploit::appendPrompt → NO_COVERAGE |
this.appendPrompt("jsql", Color.LIGHT_GRAY, isAddingPrompt); |
| 160 |
1
1. displayPrompt : removed call to javax/swing/text/StyleConstants::setUnderline → NO_COVERAGE |
StyleConstants.setUnderline(this.style, false); |
| 161 | ||
| 162 |
1
1. displayPrompt : removed call to com/jsql/view/swing/terminal/AbstractExploit::appendPrompt → NO_COVERAGE |
this.appendPrompt(StringUtils.SPACE + this.labelShell, Color.LIGHT_GRAY, isAddingPrompt); |
| 163 |
1
1. displayPrompt : removed call to com/jsql/view/swing/terminal/AbstractExploit::appendPrompt → NO_COVERAGE |
this.appendPrompt("[", new Color(0x32BF32), isAddingPrompt); |
| 164 |
1
1. displayPrompt : removed call to com/jsql/view/swing/terminal/AbstractExploit::appendPrompt → NO_COVERAGE |
this.appendPrompt(this.host, new Color(0xBFBF19), isAddingPrompt); |
| 165 |
1
1. displayPrompt : removed call to com/jsql/view/swing/terminal/AbstractExploit::appendPrompt → NO_COVERAGE |
this.appendPrompt("]", new Color(0x32BF32), isAddingPrompt); |
| 166 |
1
1. displayPrompt : removed call to com/jsql/view/swing/terminal/AbstractExploit::appendPrompt → NO_COVERAGE |
this.appendPrompt(" >", new Color(0xBF6464), isAddingPrompt); |
| 167 |
1
1. displayPrompt : removed call to com/jsql/view/swing/terminal/AbstractExploit::appendPrompt → NO_COVERAGE |
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 |
1
1. appendPrompt : removed call to javax/swing/text/StyleConstants::setForeground → NO_COVERAGE |
StyleConstants.setForeground(this.style, color); |
| 179 |
1
1. appendPrompt : removed call to javax/swing/text/StyledDocument::insertString → NO_COVERAGE |
this.getStyledDocument().insertString(this.getStyledDocument().getLength(), string, this.style); |
| 180 |
1
1. appendPrompt : negated conditional → NO_COVERAGE |
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 |
3
1. getScrollableTracksViewportWidth : negated conditional → NO_COVERAGE 2. getScrollableTracksViewportWidth : replaced boolean return with true for com/jsql/view/swing/terminal/AbstractExploit::getScrollableTracksViewportWidth → NO_COVERAGE 3. getScrollableTracksViewportWidth : changed conditional boundary → NO_COVERAGE |
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 |
2
1. getLineOfOffset : negated conditional → NO_COVERAGE 2. getLineOfOffset : changed conditional boundary → NO_COVERAGE |
if (offset < 0) { |
| 214 | throw new BadLocationException(errorMsg, -1); | |
| 215 |
2
1. getLineOfOffset : changed conditional boundary → NO_COVERAGE 2. getLineOfOffset : negated conditional → NO_COVERAGE |
} else if (offset > doc.getLength()) { |
| 216 |
1
1. getLineOfOffset : Replaced integer addition with subtraction → NO_COVERAGE |
throw new BadLocationException(errorMsg, doc.getLength() + 1); |
| 217 | } else { | |
| 218 | var map = doc.getDefaultRootElement(); | |
| 219 |
1
1. getLineOfOffset : replaced int return with 0 for com/jsql/view/swing/terminal/AbstractExploit::getLineOfOffset → NO_COVERAGE |
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 |
2
1. getLineStartOffset : negated conditional → NO_COVERAGE 2. getLineStartOffset : changed conditional boundary → NO_COVERAGE |
if (line < 0) { |
| 232 | throw new BadLocationException("Negative line", -1); | |
| 233 |
2
1. getLineStartOffset : changed conditional boundary → NO_COVERAGE 2. getLineStartOffset : negated conditional → NO_COVERAGE |
} else if (line >= map.getElementCount()) { |
| 234 |
1
1. getLineStartOffset : Replaced integer addition with subtraction → NO_COVERAGE |
throw new BadLocationException("No such line", this.getDocument().getLength() + 1); |
| 235 | } else { | |
| 236 | var lineElem = map.getElement(line); | |
| 237 |
1
1. getLineStartOffset : replaced int return with 0 for com/jsql/view/swing/terminal/AbstractExploit::getLineStartOffset → NO_COVERAGE |
return lineElem.getStartOffset(); |
| 238 | } | |
| 239 | } | |
| 240 | ||
| 241 | | |
| 242 | // Getter and setter | |
| 243 | | |
| 244 | public AtomicBoolean getIsEdited() { | |
| 245 |
1
1. getIsEdited : replaced return value with null for com/jsql/view/swing/terminal/AbstractExploit::getIsEdited → NO_COVERAGE |
return this.isEdited; |
| 246 | } | |
| 247 | ||
| 248 | public UUID getUuidShell() { | |
| 249 |
1
1. getUuidShell : replaced return value with null for com/jsql/view/swing/terminal/AbstractExploit::getUuidShell → NO_COVERAGE |
return this.uuidShell; |
| 250 | } | |
| 251 | ||
| 252 | public String getUrlShell() { | |
| 253 |
1
1. getUrlShell : replaced return value with "" for com/jsql/view/swing/terminal/AbstractExploit::getUrlShell → NO_COVERAGE |
return this.urlShell; |
| 254 | } | |
| 255 | ||
| 256 | public String getPrompt() { | |
| 257 |
1
1. getPrompt : replaced return value with "" for com/jsql/view/swing/terminal/AbstractExploit::getPrompt → NO_COVERAGE |
return this.prompt; |
| 258 | } | |
| 259 | ||
| 260 | public String[] getLoginPassword() { | |
| 261 |
1
1. getLoginPassword : replaced return value with null for com/jsql/view/swing/terminal/AbstractExploit::getLoginPassword → NO_COVERAGE |
return this.loginPassword; |
| 262 | } | |
| 263 | ||
| 264 | public void setLoginPassword(String[] loginPassword) { | |
| 265 | this.loginPassword = loginPassword; | |
| 266 | } | |
| 267 | } | |
Mutations | ||
| 89 |
1.1 |
|
| 96 |
1.1 |
|
| 97 |
1.1 |
|
| 98 |
1.1 |
|
| 99 |
1.1 |
|
| 101 |
1.1 |
|
| 102 |
1.1 |
|
| 104 |
1.1 |
|
| 107 |
1.1 |
|
| 108 |
1.1 |
|
| 109 |
1.1 |
|
| 111 |
1.1 |
|
| 112 |
1.1 |
|
| 128 |
1.1 |
|
| 131 |
1.1 |
|
| 132 |
1.1 |
|
| 133 |
1.1 |
|
| 134 |
1.1 |
|
| 136 |
1.1 |
|
| 137 |
1.1 |
|
| 147 |
1.1 |
|
| 158 |
1.1 |
|
| 159 |
1.1 |
|
| 160 |
1.1 |
|
| 162 |
1.1 |
|
| 163 |
1.1 |
|
| 164 |
1.1 |
|
| 165 |
1.1 |
|
| 166 |
1.1 |
|
| 167 |
1.1 |
|
| 178 |
1.1 |
|
| 179 |
1.1 |
|
| 180 |
1.1 |
|
| 193 |
1.1 2.2 3.3 |
|
| 213 |
1.1 2.2 |
|
| 215 |
1.1 2.2 |
|
| 216 |
1.1 |
|
| 219 |
1.1 |
|
| 231 |
1.1 2.2 |
|
| 233 |
1.1 2.2 |
|
| 234 |
1.1 |
|
| 237 |
1.1 |
|
| 245 |
1.1 |
|
| 249 |
1.1 |
|
| 253 |
1.1 |
|
| 257 |
1.1 |
|
| 261 |
1.1 |