-
Notifications
You must be signed in to change notification settings - Fork 30
/
jQuery-hook.js
356 lines (315 loc) · 12.9 KB
/
jQuery-hook.js
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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
// ==UserScript==
// @name jQuery Hook
// @namespace https://github.com/JSREI/jQuery-hook
// @version 0.4
// @description 用于快速定位使用jQuery绑定到DOM元素上的事件的代码的真实位置,辅助逆向分析。
// @document https://github.com/JSREI/jQuery-hook
// @author CC11001100
// @match *://*/*
// @run-at document-start
// @grant none
// ==/UserScript==
(() => {
// 可自定义的一个变量前缀,尽量唯一有区分度即可,可以替换为为自己的ID
const globalUniqPrefix = "cc11001100";
// 用于控制打印在控制台的消息的大小
const consoleLogFontSize = 12;
// ----------------------------------------------- -----------------------------------------------------------------
/**
* 用于统一构建待颜色的日志输出,采用构建者模式
*
* from: https://github.com/JSREI/js-color-log
*/
class ColorLogBuilder {
/**
* 创建一条日志,调用show()方法将其打印到控制台
*
* 因为认为字体颜色是没有区分度的,所以这里就不支持指定字体的颜色,字体恒定为黑色
*
* @param normalTextBackgroundColor {string} 此条日志中普通文本的背景色
* @param highlightTextBackgroundColor {string} 此条日志中要高亮的文本的背景色
* @param _consoleLogFontSize {string} 日志的大小
*/
constructor(normalTextBackgroundColor = "#FFFFFF", highlightTextBackgroundColor = "#FFFFFF", _consoleLogFontSize = consoleLogFontSize) {
this.normalTextBackgroundColor = normalTextBackgroundColor;
this.highlightTextBackgroundColor = highlightTextBackgroundColor;
this.consoleLogFontSize = _consoleLogFontSize;
this.messageArray = [];
// 每天日志都使用统一的前缀,在创建的时候就设置好
// 先是一个日期,然后是插件的名字,以便与其它工具的输出相区分
// 此处的统一前缀自行修改,因为使用的时候都是拷贝过去的
this.append(`[${this.nowTimeString()}] `).append("jQuery Hook: ");
}
/**
* 往日志中追加普通类型的信息
*
* @param msg {string}
* @return {ColorLogBuilder}
*/
append(msg) {
this.appendNormal(msg);
return this;
}
/**
* 往日志中追加普通类型的信息
*
* @param msg {string}
* @return {ColorLogBuilder}
*/
appendNormal(msg) {
this.messageArray.push(`color: black; background: ${this.normalTextBackgroundColor}; font-size: ${this.consoleLogFontSize}px;`);
this.messageArray.push(msg);
return this;
}
/**
* 往日志中追加高亮的内容
*
* @param msg {string}
*/
appendHighlight(msg) {
this.messageArray.push(`color: black; background: ${this.highlightTextBackgroundColor}; font-size: ${this.consoleLogFontSize}px; font-weight: bold;`);
this.messageArray.push(msg);
return this;
}
/**
* 把当前这条日志打印出来
*/
show() {
console.log(this.genFormatArray(this.messageArray), ...this.messageArray);
}
nowTimeString(fmt = "yyyy-MM-dd HH:mm:ss") {
const now = new Date();
let o = {
"M+": now.getMonth() + 1, "d+": now.getDate(), //日
"H+": now.getHours(), //小时
"m+": now.getMinutes(), //分
"s+": now.getSeconds(), //秒
"q+": Math.floor((now.getMonth() + 3) / 3), //季度
"S": now.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (now.getFullYear() + "").substr(4 - RegExp.$1.length));
for (let k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
genFormatArray(messageAndStyleArray) {
const formatArray = [];
for (let i = 0, end = messageAndStyleArray.length / 2; i < end; i++) {
formatArray.push("%c%s");
}
return formatArray.join("");
}
}
// ----------------------------------------------- -----------------------------------------------------------------
// 在第一次初始化jQuery的时候添加Hook,jQuery初始化的时候会添加一个名为$的全局变量,在添加这个变量的时候对其动一些手脚
Object.defineProperty(window, "$", {
set: $ => {
// 为jquery的各种方法添加Hook
try {
addHook($);
} catch (e) {
new ColorLogBuilder("#FF6766", "#E50000")
.append("add hook error, msg = ")
.appendHighlight(e)
.show();
}
// 删除set描述符拦截,恢复正常赋值,假装啥都没发生过,但实际上已经狸猫换太子了...
delete window["$"];
window["$"] = $;
}, configurable: true
});
/**
* 为jquery添加一些hook,等会儿使用jquery为dom元素绑定事件的话就会被捕获到
* @param $
*/
function addHook($) {
addEventHook($);
addAjaxHook($);
new ColorLogBuilder("#65CC66", "#669934")
.append("在当前页面上检测到jQuery的加载,添加jQuery Hook完成")
.show();
}
/**
* 增加Ajax Hook
*
* @param $
*/
function addAjaxHook($) {
if (!$["ajaxSetup"]) {
new ColorLogBuilder("#FF6766", "#E50000")
.appendHighlight("$不是jQuery对象,没有 ajaxSetup 属性,因此不添加Ajax Hook")
.show();
return;
}
const oldAjaxSetUp = $.ajaxSetup;
$.ajaxSetup = function () {
try {
if (arguments.length === 1) {
const {formatEventName, eventFuncGlobalName} = storeToWindow("ajaxSetup", arguments[0]);
new ColorLogBuilder("#65CC66", "#669934")
.append("检测到ajaxSetup全局拦截器设置请求参数,已经挂载到全局变量:")
.appendHighlight(eventFuncGlobalName)
.show();
}
} catch (e) {
console.error(e);
}
return oldAjaxSetUp.apply(this, arguments);
}
}
/**
* 增加事件Hook
*
* @param $
*/
function addEventHook($) {
if (!$["fn"]) {
new ColorLogBuilder("#FF6766", "#E50000")
.appendHighlight("$不是jQuery对象,没有 fn 属性,因此不添加 Event Hook")
.show();
return;
}
// 一些比较通用的事件的拦截
const eventNameList = ["click", "dblclick", "blur", "change", "contextmenu", "error", "focus", "focusin", "focusout", "hover", "holdReady", "proxy", "ready", "keydown", "keypress", "keyup", "live", "load", "mousedown", "mouseenter", "mouseleave", "mousemove", "mouseout", "mouseover", "mouseup"];
for (let eventName of eventNameList) {
const old = $.fn[eventName];
$.fn[eventName] = function () {
try {
setEventFunctionNameToDomObjectAttribute(this, eventName, arguments[0]);
} catch (e) {
new ColorLogBuilder("#FF6766", "#E50000")
.appendHighlight(`为jQuery添加${eventName}类型的事件的Hook时发生错误: ${e}`)
.show();
}
return old.apply(this, arguments);
}
}
// on,不仅是内置事件类型,还有可能有一些自定义的事件类型
// https://api.jquery.com/on/
const fnOnHolder = $.fn.on;
$.fn.on = function () {
try {
const eventName = arguments[0];
let eventFunction = undefined;
for (let x of arguments) {
if (x instanceof Function) {
eventFunction = x;
break;
}
}
if (eventFunction instanceof Function) {
setEventFunctionNameToDomObjectAttribute(this, eventName, eventFunction);
}
} catch (e) {
new ColorLogBuilder("#FF6766", "#E50000")
.appendHighlight(`为jQuery添加on方法的Hook时发生错误: ${e}`)
.show();
}
return fnOnHolder.apply(this, arguments);
}
// TODO 还有delegate之类的比较隐晦的绑定事件的方式
}
/**
* 为绑定了jquery事件的dom元素添加元素,提示所绑定的事件与对应的函数代码的全局变量的名称,只需要复制粘贴跟进去即可
* 注意,有可能会为同一个元素重复绑定相同的事件
*
* @param domObject
* @param eventName
* @param eventFunction
*/
function setEventFunctionNameToDomObjectAttribute(domObject, eventName, eventFunction) {
eventName.split(' ').map((eventName) => {
const {formatEventName, eventFuncGlobalName} = storeToWindow(eventName, eventFunction);
const attrName = `${globalUniqPrefix}-jQuery-${formatEventName}-event-function`;
if (domObject.attr(attrName)) {
domObject.attr(attrName + "-" + new Date().getTime(), eventFuncGlobalName);
} else {
domObject.attr(attrName, eventFuncGlobalName);
}
})
}
// ----------------------------------------------- -----------------------------------------------------------------
// 用于缓存事件函数到全局变量的映射关系
// <事件函数, 全局变量>
const eventFuncCacheMap = new Map();
/**
* 为事件的函数绑定一个全局变量,如果之前已经绑定过了则返回之前的
*
* @param eventName {string}
* @param eventFunc {Function}
* @return {{string, string}} 事件名和其对应的函数绑定到的全局变量
*/
function storeToWindow(eventName, eventFunc) {
if (eventFunc in eventFuncCacheMap) {
return eventFuncCacheMap[eventFunc];
}
// 注意,事件名可能会包含一些非法的字符,所以需要转义
// cc11001100-jquery-$destroy-event-function
const formatEventName = safeSymbol(eventName);
const eventFuncGlobalName = globalUnique(formatEventName);
window[eventFuncGlobalName] = eventFunc;
eventFuncCacheMap[eventFunc] = eventFuncGlobalName;
return {
formatEventName, eventFuncGlobalName,
};
}
/***
* 将事件名称转为合法的变量名称
*
* @param name
*/
function safeSymbol(name) {
const replaceMap = {
".": "_dot_",
"$": "_dollar_",
"-": "_dash_",
" ": "_whitespace_"
};
let newName = "";
for (let c of name) {
if (c in replaceMap) {
newName += replaceMap[c];
} else if (isOkVarChar(c)) {
newName += c;
}
}
return newName;
}
/**
* 判断字符是否是合法的变量名字符
*
* @param c {string}
* @returns {boolean}
*/
function isOkVarChar(c) {
return (/^[a-zA-Z0-9]$/).test(c);
}
// ----------------------------------------------- -----------------------------------------------------------------
// 每个事件一个独立的自增id
const addressIdGeneratorMap = {};
/**
* 为给定的事件生成一个全局唯一的标识,这个标识中会带上事件类型以方便区分不同事件
*
* @param eventName {string}
*/
function globalUnique(eventName) {
const id = (addressIdGeneratorMap[eventName] || 0) + 1;
addressIdGeneratorMap[eventName] = id;
return `${globalUniqPrefix}__${eventName}__${id}`;
}
// ----------------------------------------------- -----------------------------------------------------------------
/**
* 解析当前代码的位置,以便能够直接定位到事件触发的代码位置
*
* @param keyword {string}
* @returns {string}
*/
function getCodeLocation(keyword = "cc11001100") {
const callstack = new Error().stack.split("\n");
while (callstack.length && callstack[0].indexOf(keyword) === -1) {
callstack.shift();
}
callstack.shift();
// callstack.shift();
return callstack[0].trim();
}
})();