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.util;
12  
13  import com.jsql.util.bruter.Base16;
14  import com.jsql.util.bruter.Base58;
15  import org.apache.commons.codec.binary.Base32;
16  import org.apache.commons.lang3.ArrayUtils;
17  import org.apache.commons.lang3.StringUtils;
18  import org.apache.commons.text.StringEscapeUtils;
19  import org.apache.logging.log4j.LogManager;
20  import org.apache.logging.log4j.Logger;
21  import org.mozilla.universalchardet.UniversalDetector;
22  
23  import java.awt.*;
24  import java.io.*;
25  import java.net.URLDecoder;
26  import java.net.URLEncoder;
27  import java.nio.charset.StandardCharsets;
28  import java.util.ArrayList;
29  import java.util.Base64;
30  import java.util.List;
31  import java.util.Objects;
32  import java.util.zip.DeflaterOutputStream;
33  import java.util.zip.InflaterOutputStream;
34  
35  /**
36   * Utility class adding String operations like join() which are not
37   * part of standard JVM.
38   */
39  public final class StringUtil {
40      
41      private static final Logger LOGGER = LogManager.getRootLogger();
42      
43      // Define the schema of conversion to html entities
44      private static final CharEncoder DECIMAL_HTML_ENCODER = new CharEncoder("&#", ";", 10);
45      public static final String GET = "GET";
46      public static final String POST = "POST";
47      public static final String INFORMATION_SCHEMA = "information_schema";
48      public static final String APP_NAME = "jSQL Injection";
49  
50      /**
51       * This utility class defines a schema used to encode a text into a specialized
52       * representation
53       */
54      private static class CharEncoder {
55          
56          private final String prefix;
57          private final String suffix;
58          private final int radix;
59          
60          public CharEncoder(String prefix, String suffix, int radix) {
61              this.prefix = prefix;
62              this.suffix = suffix;
63              this.radix = radix;
64          }
65          
66          protected void encode(char c, StringBuilder buff) {
67              buff
68              .append(this.prefix)
69              .append(Integer.toString(c, this.radix))
70              .append(this.suffix);
71          }
72      }
73  
74      private StringUtil() {
75          // Utility class
76      }
77      
78      /**
79       * Convert special characters like Chinese and Arabic letters to the corresponding html entities.
80       * @param text string to encode
81       * @return string encoded in html entities
82       */
83      public static String toHtmlDecimal(String text) {
84          return StringUtil.encode(text);
85      }
86      
87      /**
88       * Non-trivial methods to convert unicode characters to html entities.
89       * @param text string to encode
90       * @return string representation using the encoder schema
91       */
92      private static String encode(String text) {
93          var buff = new StringBuilder();
94          for (var i = 0 ; i < text.length() ; i++) {
95              if (text.charAt(i) > 128) {
96                  StringUtil.DECIMAL_HTML_ENCODER.encode(text.charAt(i), buff);
97              } else {
98                  buff.append(text.charAt(i));
99              }
100         }
101         return buff.toString();
102     }
103 
104     /**
105      * Convert a hexadecimal String to String.
106      * @param hex Hexadecimal String to convert
107      * @return The string converted from hex
108      */
109     public static String hexstr(String hex) {
110         var bytes = new byte[hex.length() / 2];
111         for (var i = 0 ; i < bytes.length ; i++) {
112             bytes[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
113         }
114         return new String(bytes, StandardCharsets.UTF_8);
115     }
116     
117     public static boolean isUtf8(String text) {
118         if (text == null) {
119             return false;
120         }
121         
122         var detector = new UniversalDetector(null);
123         detector.handleData(text.getBytes(StandardCharsets.UTF_8), 0, text.length() - 1);
124         detector.dataEnd();
125         String encoding = detector.getDetectedCharset();
126         return encoding != null;
127     }
128     
129     public static String detectUtf8(String text) {
130         if (text == null) {
131             return StringUtils.EMPTY;
132         }
133         
134         String encoding = null;
135         
136         // ArrayIndexOutOfBoundsException on handleData()
137         try {
138             var detector = new UniversalDetector(null);
139             detector.handleData(text.getBytes(StandardCharsets.UTF_8), 0, text.length() - 1);
140             detector.dataEnd();
141             encoding = detector.getDetectedCharset();
142             
143         } catch (ArrayIndexOutOfBoundsException e) {
144             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
145         }
146         
147         String result = text;
148         if (encoding != null) {
149             result = new String(text.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
150         }
151         return result;
152     }
153     
154     public static String base32Encode(String s) {
155         var base32 = new Base32();
156         return base32.encodeToString(StringUtil.getBytesUtf8(s));
157     }
158     
159     public static String base32Decode(String s) {
160         var base32 = new Base32();
161         return StringUtil.newStringUtf8(base32.decode(s));
162     }
163     
164     public static String base58Encode(String s) {
165         return Base58.encode(StringUtil.getBytesUtf8(s));
166     }
167     
168     public static String base58Decode(String s) {
169         return StringUtil.newStringUtf8(Base58.decode(s));
170     }
171     
172     public static String base16Encode(String s) {
173         var base16 = new Base16();
174         return base16.encodeToString(StringUtil.getBytesUtf8(s));
175     }
176     
177     public static String base16Decode(String s) {
178         var base16 = new Base16();
179         return StringUtil.newStringUtf8(base16.decode(s));
180     }
181 
182     public static String base64Decode(String s) {
183         return StringUtil.newStringUtf8(Base64.getDecoder().decode(s));
184     }
185 
186     public static String base64Encode(String s) {
187         return Base64.getEncoder().encodeToString(StringUtil.getBytesUtf8(s));
188     }
189 
190     public static String toHex(String text) {
191         return StringUtil.encodeHexString(text.getBytes(StandardCharsets.UTF_8));
192     }
193     
194     public static String fromHex(String text) {
195         byte[] hex = StringUtil.decodeHexString(text);
196         return new String(hex, StandardCharsets.UTF_8);
197     }
198     
199     public static String toHexZip(String text) throws IOException {
200         byte[] zip = StringUtil.compress(text);
201         return StringUtil.encodeHexString(zip);
202     }
203 
204     public static String fromHexZip(String text) throws IOException {
205         return new String(StringUtil.decompress(StringUtil.decodeHexString(text)), StandardCharsets.UTF_8);
206     }
207     
208     public static String toBase64Zip(String text) throws IOException {
209         return new String(Base64.getEncoder().encode(StringUtil.compress(text)));
210     }
211     
212     public static String fromBase64Zip(String text) throws IOException {
213         byte[] decompressedBArray = StringUtil.decompress(Base64.getDecoder().decode(text));
214         return new String(decompressedBArray, StandardCharsets.UTF_8);
215     }
216     
217     public static String toHtml(String text) {
218         return StringEscapeUtils.escapeHtml4(text);
219     }
220     
221     public static String fromHtml(String text) {
222         return StringEscapeUtils.unescapeHtml4(text);
223     }
224     
225     public static String toUrl(String text) {
226         return URLEncoder.encode(text, StandardCharsets.UTF_8);
227     }
228     
229     public static String fromUrl(String text) {
230         return URLDecoder.decode(text, StandardCharsets.UTF_8);
231     }
232     
233     public static String cleanSql(String query) {
234         return StringUtil.removeSqlComment(query)
235             .replaceAll("(?s)([^\\s\\w])(\\s+)", "$1")  // Remove spaces after a word
236             .replaceAll("(?s)(\\s+)([^\\s\\w])", "$2")  // Remove spaces before a word
237             .replaceAll("(?s)\\s+", " ")  // Replace spaces
238             .trim();
239     }
240 
241     /**
242      * Remove SQL comments except tamper /**\/ /*!...*\/
243      * Negative lookahead: don't match tamper empty comment /**\/ or version comment /*!...*\/
244      * JavaScript: (?!\/\*!.*\*\/|\/\*\*\/)\/\*.*\*\/
245      */
246     public static String removeSqlComment(String query) {
247         return query.replaceAll(
248             "(?s)(?!/\\*\\*/|/\\*!.*\\*/)/\\*.*?\\*/",
249             StringUtils.EMPTY
250         );
251     }
252 
253     public static String formatReport(Color color, String text) {
254         return String.format(
255             "<span style=color:rgb(%s,%s,%s)>%s</span>",
256             color.getRed(),
257             color.getGreen(),
258             color.getBlue(),
259             text
260         );
261     }
262 
263 
264     // Utils
265 
266     private static byte[] compress(String text) throws IOException {
267         ByteArrayOutputStream os = new ByteArrayOutputStream();
268         try (DeflaterOutputStream dos = new DeflaterOutputStream(os)) {
269             dos.write(text.getBytes());
270         }
271         return os.toByteArray();
272     }
273 
274     private static byte[] decompress(byte[] compressedTxt) throws IOException {
275         ByteArrayOutputStream os = new ByteArrayOutputStream();
276         try (OutputStream ios = new InflaterOutputStream(os)) {
277             ios.write(compressedTxt);
278         }
279         return os.toByteArray();
280     }
281 
282     private static byte hexToByte(String hexString) {
283         int firstDigit = StringUtil.toDigit(hexString.charAt(0));
284         int secondDigit = StringUtil.toDigit(hexString.charAt(1));
285         return (byte) ((firstDigit << 4) + secondDigit);
286     }
287 
288     private static int toDigit(char hexChar) {
289         int digit = Character.digit(hexChar, 16);
290         if (digit == -1) {
291             throw new IllegalArgumentException("Invalid Hexadecimal Character: "+ hexChar);
292         }
293         return digit;
294     }
295 
296     private static String byteToHex(byte num) {
297         char[] hexDigits = new char[2];
298         hexDigits[0] = Character.forDigit((num >> 4) & 0xF, 16);
299         hexDigits[1] = Character.forDigit(num & 0xF, 16);
300         return new String(hexDigits);
301     }
302 
303     private static String encodeHexString(byte[] byteArray) {
304         StringBuilder hexStringBuffer = new StringBuilder();
305         for (byte b : byteArray) {
306             hexStringBuffer.append(StringUtil.byteToHex(b));
307         }
308         return hexStringBuffer.toString();
309     }
310 
311     private static byte[] decodeHexString(String hexString) {
312         if (hexString.length() % 2 == 1) {
313             throw new IllegalArgumentException("Invalid hexadecimal String supplied.");
314         }
315         byte[] bytes = new byte[hexString.length() / 2];
316         for (int i = 0 ; i < hexString.length() ; i += 2) {
317             bytes[i / 2] = StringUtil.hexToByte(hexString.substring(i, i + 2));
318         }
319         return bytes;
320     }
321 
322     private static byte[] getBytesUtf8(String string) {
323         return string == null ? null : string.getBytes(StandardCharsets.UTF_8);
324     }
325 
326     private static String newStringUtf8(byte[] bytes) {
327         return bytes == null ? null : new String(bytes, StandardCharsets.UTF_8);
328     }
329 
330     public static byte[] xor(byte[] plaintext, int key) {
331         var ciphertext = new byte[plaintext.length];
332         for (var i = 0 ; i < plaintext.length ; i++) {
333             ciphertext[i] = (byte) (plaintext[i] ^ (key >>> (8 * (i % 4))));
334         }
335         return ciphertext;
336     }
337 
338     public static List<String> toHexChunks(byte[] fileData) {
339         StringBuilder hexString = new StringBuilder();
340         for (byte b : fileData) {
341             hexString.append(String.format("%02X", b));
342         }
343         int chunkSize = 900; // 450 bytes = 900 hex characters
344         List<String> chunks = new ArrayList<>();
345         for (int i = 0 ; i < hexString.length() ; i += chunkSize) {
346             int endIndex = Math.min(i + chunkSize, hexString.length());
347             chunks.add(hexString.substring(i, endIndex));
348         }
349         return chunks;
350     }
351 
352     public static String getFile(String path) {
353         var content = new StringBuilder();
354         try (
355             var inputStream = PreferencesUtil.class.getClassLoader().getResourceAsStream(path);
356             var inputStreamReader = new InputStreamReader(Objects.requireNonNull(inputStream), StandardCharsets.UTF_8);
357             var reader = new BufferedReader(inputStreamReader)
358         ) {
359             String line;
360             while ((line = reader.readLine()) != null) {
361                 content.append(line).append("\n");  // required to prevent \n<text>\r on edit
362             }
363         } catch (IOException e) {
364             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
365         }
366         return content.toString();
367     }
368 
369     public static byte[] uncloak(byte[] fileData) {
370         fileData = StringUtil.xor(fileData, 353837730);
371         ArrayUtils.reverse(fileData);
372         return fileData;
373     }
374 }