zj
2025-05-06 0a5de93f4212f260ddc65c9317632429e828c7dd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
/**
 * Copyright (c) 2015,TravelSky. 
 * All Rights Reserved.
 * TravelSky CONFIDENTIAL
 * 
 * Project Name:ShoppinguiOnlineScript
 * Package Name:com.travelsky.shoppingui.utils
 * File Name:JavaScriptUtil.java
 * Date:2015年6月8日 下午2:35:12
 * 
 */
package com.ruoyi.web.controller.tool;
 
 
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
 
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
 
 
/**
 * JavaScript工具类。
 * 
 * @author Michael J Chane
 * @version $Revision: 1.2 $ $Date: 2009/09/10 15:08:30 $
 */
public final class JavaScriptUtil {
 
  /**
   * 特殊字符
   */
  private static final String SPECIAL_CHARACTERS = "\n\r\f\'\"\\";
 
  /**
   * 特殊字符-转义字符的映射
   */
  private static final Properties ESCAPE_MAP = new Properties();
  static {
    ESCAPE_MAP.put("\n", "\\n");
    ESCAPE_MAP.put("\r", "\\r");
    ESCAPE_MAP.put("\f", "\\f");
    ESCAPE_MAP.put("\'", "\\\'");
    ESCAPE_MAP.put("\"", "\\\"");
    ESCAPE_MAP.put("\\", "\\\\");
  }
 
  /**
   * 不需要实例化
   */
  private JavaScriptUtil() {
  }
 
  /**
   * 对JavaScript字符串内部的文本进行转义操作,避免字符串内的特殊字符影响JavaScript代码的执行。
   * 
   * @param text
   *          要转义的字符串文本
   * @return 转义后的字符串文本
   */
  public static String escapeInStringLiterals(CharSequence text) {
    // 对null返回空白字符串
    if (text == null) {
      return "";
    }
 
    // 字符串缓冲
    StringBuilder buff = new StringBuilder(text.length() + 16);
    // 以特殊字符为分隔符,将文本分段(含特殊字符)
    StringTokenizer st = new StringTokenizer(text.toString(),
        SPECIAL_CHARACTERS, true);
 
    while (st.hasMoreTokens()) {
      // 当前片段
      String token = st.nextToken();
      // 如果是特殊字符则转义,否则返回本身
      buff.append(ESCAPE_MAP.getProperty(token, token));
    }
 
    return buff.toString();
  }
 
  /**
   * 将指定的JavaScript代码进行混淆。
   * 
   * @param script
   *          指定的JavaScript代码
   * @return 混淆后的JavaScript代码
   */
  public static String obfuscateScript(CharSequence script) {
    if (script == null) {
      return "";
    }
 
    // String.fromCharCode中参数最大个数
    final int stringFromCharCodeLimit = 100;
    // 每行的参数个数
    final int parametersPerLine = 10;
    // 使用xor函数的比例
    final float xorRate = 0.1f;
    // 字符缓冲
    StringBuilder buff = new StringBuilder(script.length() * 10 + 500);
    // 格式化输出到字符串缓冲
    Formatter formatter = new Formatter(buff);
 
    // 输出String.fromCharCode的别名定义,并返回其别名
    String stringFromCharCode = stringFromCharCode(formatter);
    // 输出xor函数,并返回函数名列表及对应的xor阈值
    Map<String, Integer> xorFunctions = xorFunctions(formatter);
    // xor函数名称
    String[] xorFuncNames = xorFunctions.keySet().toArray(new String[0]);
 
    // eval函数开始,其中第一个使用String.fromCharCode(32)即空格
    formatter.format("/*%2$s*/\\u0065\\u0076\\u0061\\u006c/*%3$s*/(%1$s(32",
                     formatArguments(3, stringFromCharCode));
 
    // 遍历代码中的所有字符
    for (int i = 0; i < script.length(); i++) {
      // 当前字符
      int code = script.charAt(i);
 
      if (i % stringFromCharCodeLimit == 0) {
        // 结束旧的String.fromCharCode,
        formatter.format(")%n");
        // 开始新的String.fromCharCode
        formatter.format("+/*%2$s*/%1$s(", formatArguments(2,
                                                           stringFromCharCode));
      } else {
        // 一般的String.fromCharCode参数之间使用逗号分隔
        buff.append(",");
        if (i % parametersPerLine == 0) {
          // 当前字符结束后需要换行
          formatter.format("%n");
        }
      }
 
      // 根据xorRate确定的比例,输出当前字符参数
      if (RandomUtils.nextFloat() < xorRate) {
        // 使用xor参数的名称
        String xorFunc = xorFuncNames[i % xorFuncNames.length];
        // 对应的异或计算阈值
        int xor = xorFunctions.get(xorFunc);
        // 进行过异或计算后的结果
        int xorCode = code ^ xor;
        // 输出函数调用
        formatter.format("%1$s(", xorFunc);
        // 输出函数参数
        formatter.format(numberFormat(i), xorCode);
        // 调用结束
        buff.append(")");
      } else {
        // 正常输出
        formatter.format(numberFormat(i), code);
      }
    }
 
    // 最后一个String.fromCharCode和eval函数的结尾
    formatter.format("/*%1$s%2$s*/));/*%3$s*/%n", formatArguments(3));
 
    // 返回混淆代码
    return buff.toString();
  }
 
  /**
   * 输出String.fromCharCode的别名定义,并返回其别名。
   * 
   * @param formatter
   *          格式化输出
   * @return String.fromCharCode别名
   */
  private static String stringFromCharCode(Formatter formatter) {
    String stringFromCharCode = "__" + randomAlphanumeric(3, 10);
    formatter
        .format("/*%2$s*/var/*%3$s*/%1$s/*%4$s*/=\\u0053\\u0074\\u0072\\u0069\\u006e\\u0067%n/*%5$s*/./*%6$s*/\\u0066r\\u006fm\\u0043ha\\u0072C\\u006fde/*%7$s*/;%n",
                formatArguments(7, stringFromCharCode));
    return stringFromCharCode;
  }
 
  /**
   * 输出xor函数,并返回函数名列表及对应的xor阈值。
   * 
   * @param formatter
   *          格式化输出
   * @return xor函数名列表及对应的xor阈值
   */
  private static Map<String, Integer> xorFunctions(Formatter formatter) {
    int[] xorArray = new int[5];
    for (int i = 0; i < xorArray.length; i++) {
      xorArray[i] = RandomUtils.nextInt();
    }
    String xorArrayName = "_x_" + randomAlphanumeric(3);
    formatter.format("var/*%2$s*/%1$s = [/*%3$s*/",
                     formatArguments(3, xorArrayName));
    for (int i = 0; i < xorArray.length; i++) {
      formatter.format("%d,", xorArray[i]);
    }
    formatter.format("/*%s*/];//%s%n", formatArguments(2));
 
    Map<String, Integer> functions = new HashMap<String, Integer>();
    for (int i = 0; i < xorArray.length; i++) {
      String func = "_$" + randomAlphanumeric(3, 5);
      formatter.format("var/*%2$s*/%1$s/*%3$s*/=/*%4$s*/function(/*%5$s*/){%n",
                       formatArguments(5, func));
      formatter.format("/*%1$s*/return/*%2$s*/arguments[/*%3$s*/0]^/*%4$s*/%n",
                       formatArguments(4));
      formatter.format("/*%3$s*/%1$s[/*%4$s*/%2$d];/*%5$s*/}/*%6$s*/;%n",
                       formatArguments(6, xorArrayName, i));
      functions.put(func, xorArray[i]);
    }
    return functions;
  }
 
  /**
   * 获取格式化输出参数。
   * 
   * @param count
   *          参数个数
   * @param firstFixedParameters
   *          最初的固定参数
   * @return 根据给定的参数个数,在最初的固定参数后生成随机字符串作为格式化输出参数
   */
  private static Object[] formatArguments(int count,
                                          Object... firstFixedParameters) {
    if (count < firstFixedParameters.length) {
      throw new IllegalArgumentException("length < codes.length");
    }
    Object[] args = new Object[count];
    System.arraycopy(firstFixedParameters,
                     0,
                     args,
                     0,
                     firstFixedParameters.length);
    for (int i = firstFixedParameters.length; i < args.length; i++) {
      args[i] = randomAlphanumeric(5, 20);
    }
    return args;
  }
 
  /**
   * 生成由字母及数字组成的随机字符串。
   * 
   * @param length
   *          随机字符串长度
   * @return 由字母及数字组成的随机字符串
   */
  private static String randomAlphanumeric(int length) {
    return randomAlphanumeric(length, length);
  }
 
  /**
   * 生成由字母及数字组成的随机字符串。
   * 
   * @param minLength
   *          随机字符串最小长度
   * @param maxLength
   *          随机字符串最大长度
   * @return 由字母及数字组成的随机字符串
   */
  private static String randomAlphanumeric(int minLength, int maxLength) {
    if (minLength <= 0) {
      throw new IllegalArgumentException("minLength <= 0");
    }
    if (maxLength <= 0) {
      throw new IllegalArgumentException("maxLength <= 0");
    }
    if (minLength > maxLength) {
      throw new IllegalArgumentException("minLength > maxLength");
    } else if (minLength == maxLength) {
      return RandomStringUtils.randomAlphanumeric(minLength);
    } else {
      int length = minLength + RandomUtils.nextInt();
      return RandomStringUtils.randomAlphanumeric(length);
    }
  }
 
  /**
   * 随机的格式化输出格式。
   * 
   * @param seed
   *          随机种子
   * @return 随机的格式化输出格式
   */
  private static String numberFormat(int seed) {
    int rnd = RandomUtils.nextInt();
    switch (rnd % 17) {
      case 0:
        return "0x%x";
      case 1:
        return String.format("-1-~/*%s*/(0x%%x^0)", randomAlphanumeric(2, 5));
      case 2:
        return String.format("%%d%d/0xA", RandomUtils.nextInt());
      case 3:
        return "Math.abs(%d)&-1";
      case 4:
        return "0%o";
      case 5:
        return "%d&(-1^0x00)";
      case 6:
        return "0x0|0x%x";
      case 7:
        return String.format("~/*%s*/~/*%s*/%%d", formatArguments(2));
      case 8:
        return String.format("~(0x%%x^/*%s*/-1)", randomAlphanumeric(2, 5));
      case 9:
        return String.format("0x%%x%d%d/0400",
                             RandomUtils.nextInt(),
                             RandomUtils.nextInt());
      case 10:
        return String.format("0x%%x%d%d>>/*%s*/4>>4",
                             formatArguments(3,
                                             RandomUtils.nextInt(),
                                             RandomUtils.nextInt()));
      case 11:
        return String.format("%%d/*%s*/", randomAlphanumeric(2));
      default:
        return "%d";
    }
  }
}