JS Cookie Monitor/Debugger Hook

用于监控js对cookie的修改,或者在cookie符合给定条件时进入断点

目前為 2021-01-07 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name JS Cookie Monitor/Debugger Hook
  3. // @namespace https://github.com/CC11001100/crawler-js-hook-framework-public
  4. // @version 0.2
  5. // @description 用于监控js对cookie的修改,或者在cookie符合给定条件时进入断点
  6. // @document https://github.com/CC11001100/crawler-js-hook-framework-public/tree/master/001-cookie-hook
  7. // @author CC11001100
  8. // @match *://*/*
  9. // @run-at document-start
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (() => {
  14.  
  15. // 在cookie的值发生了改变,并且cookie的名字等于任意一个给定的关键词时进入断点
  16. const debuggerOnChangeAndCookieNameEquals = [];
  17.  
  18. // 在cookie的值发生了改变,并且cookie的名字匹配给定的任意一个正则,则进入断点
  19. const debuggerOnChangeAndCookieNameRegex = [];
  20.  
  21. // 在cookie的值发生了改变,并且cookie的值符合任意一个给定的正则,则进入断点
  22. const debuggerOnChangeAndCookieValueRegex = [];
  23.  
  24. // 使用document.cookie更新cookie,但是cookie新的值和原来的值一样,此时要不要忽略这个事件
  25. const ignoreUpdateButNotChanged = false;
  26.  
  27. (function addCookieHook() {
  28. Object.defineProperty(document, "cookie", {
  29. get: () => {
  30. delete document.cookie;
  31. const currentDocumentCookie = document.cookie;
  32. addCookieHook();
  33. return currentDocumentCookie;
  34. },
  35. set: newValue => {
  36. cc11001100_onSetCookie(newValue);
  37. delete document.cookie;
  38. document.cookie = newValue;
  39. addCookieHook();
  40. },
  41. configurable: true
  42. });
  43. })();
  44.  
  45. /**
  46. * 这个方法的前缀起到命名空间的作用,等下调用栈追溯赋值cookie的代码时需要用这个名字作为终结标志
  47. *
  48. * @param newValue
  49. */
  50. function cc11001100_onSetCookie(newValue) {
  51. const cookiePair = parseSetCookie(newValue);
  52. const currentCookieMap = getCurrentCookieMap();
  53.  
  54. // 如果过期时间为当前时间之前,则为删除
  55. if (cookiePair.expires !== 0 && new Date().getTime() >= cookiePair.expires) {
  56. onDeleteCookie(cookiePair.name, cookiePair.value || (currentCookieMap.get(cookiePair.name) || {}).value);
  57. return;
  58. }
  59.  
  60. // 如果之前已经存在,则是修改
  61. if (currentCookieMap.has(cookiePair.name)) {
  62. onCookieUpdate(cookiePair.name, currentCookieMap.get(cookiePair.name).value, cookiePair.value);
  63. return;
  64. }
  65.  
  66. // 否则则为添加
  67. onCookieAdd(cookiePair.name, cookiePair.value);
  68. }
  69.  
  70. /**
  71. * 删除cookie
  72. *
  73. * @param cookieName
  74. * @param cookieValue
  75. */
  76. function onDeleteCookie(cookieName, cookieValue) {
  77. const valueStyle = "color: black; background: #E50000; font-size: 13px; font-weight: bold;";
  78. const normalStyle = "color: black; background: #FF6766; font-size: 13px;";
  79.  
  80. const message = [
  81.  
  82. normalStyle,
  83. now(),
  84.  
  85. normalStyle,
  86. "JS Cookie Monitor: ",
  87.  
  88. normalStyle,
  89. "delete cookie, cookieName = ",
  90.  
  91. valueStyle,
  92. `${cookieName}`,
  93.  
  94. ...(() => {
  95. if (!cookieValue) {
  96. return [];
  97. }
  98. return [
  99. normalStyle,
  100. ", value = ",
  101.  
  102. valueStyle,
  103. `${cookieValue}`,
  104. ];
  105. })(),
  106.  
  107. normalStyle,
  108. `, code location = ${getCodeLocation()}`
  109. ];
  110. console.log(genFormatArray(message), ...message);
  111.  
  112. isNeedDebuggerByCookieName(cookieName);
  113. }
  114.  
  115. /**
  116. * 更新cookie
  117. *
  118. * @param cookieName
  119. * @param oldCookieValue
  120. * @param newCookieValue
  121. */
  122. function onCookieUpdate(cookieName, oldCookieValue, newCookieValue) {
  123.  
  124. const cookieValueChanged = oldCookieValue !== newCookieValue;
  125.  
  126. if (ignoreUpdateButNotChanged && !cookieValueChanged) {
  127. return;
  128. }
  129.  
  130. const valueStyle = "color: black; background: #FE9900; font-size: 13px; font-weight: bold;";
  131. const normalStyle = "color: black; background: #FFCC00; font-size: 13px;";
  132.  
  133. const message = [
  134.  
  135. normalStyle,
  136. now(),
  137.  
  138. normalStyle,
  139. "JS Cookie Monitor: ",
  140.  
  141. normalStyle,
  142. "update cookie, name = ",
  143.  
  144. valueStyle,
  145. `${cookieName}`,
  146.  
  147. normalStyle,
  148. `, oldValue = `,
  149.  
  150. valueStyle,
  151. `${oldCookieValue}`,
  152.  
  153. normalStyle,
  154. `, newValue = `,
  155.  
  156. valueStyle,
  157. `${newCookieValue}`,
  158.  
  159. normalStyle,
  160. `, value changed = `,
  161.  
  162. valueStyle,
  163. `${cookieValueChanged}`,
  164.  
  165. normalStyle,
  166. `, code location = ${getCodeLocation()}`
  167. ];
  168. console.log(genFormatArray(message), ...message);
  169.  
  170. isNeedDebuggerByCookieName(cookieName);
  171. isNeedDebuggerByCookieValue(newCookieValue);
  172. }
  173.  
  174. /**
  175. * 添加cookie
  176. *
  177. * @param cookieName
  178. * @param cookieValue
  179. */
  180. function onCookieAdd(cookieName, cookieValue) {
  181. const valueStyle = "color: black; background: #669934; font-size: 13px; font-weight: bold;";
  182. const normalStyle = "color: black; background: #65CC66; font-size: 13px;";
  183.  
  184. const message = [
  185.  
  186. normalStyle,
  187. now(),
  188.  
  189. normalStyle,
  190. "JS Cookie Monitor: ",
  191.  
  192. normalStyle,
  193. "add cookie, ",
  194.  
  195. valueStyle,
  196. `${cookieName}`,
  197.  
  198. normalStyle,
  199. " = ",
  200.  
  201. valueStyle,
  202. `${cookieValue}`,
  203.  
  204. normalStyle,
  205. `, code location = ${getCodeLocation()}`
  206. ];
  207. console.log(genFormatArray(message), ...message);
  208.  
  209. isNeedDebuggerByCookieName(cookieName);
  210. isNeedDebuggerByCookieValue(cookieValue);
  211. }
  212.  
  213. // 根据cookie名字判断你是否需要进入断点
  214. function isNeedDebuggerByCookieName(cookieName) {
  215.  
  216. // 名称完全匹配
  217. for(let x of debuggerOnChangeAndCookieNameEquals) {
  218. if (cookieName === x) {
  219. debugger;
  220. }
  221. }
  222.  
  223. // 正则匹配
  224. for(let x of debuggerOnChangeAndCookieNameRegex) {
  225. if (x.test(cookieName)) {
  226. debugger;
  227. }
  228. }
  229. }
  230.  
  231. // 根据cookie值判断是否需要进入断点
  232. function isNeedDebuggerByCookieValue(cookieValue) {
  233. // 正则匹配
  234. for(let x of debuggerOnChangeAndCookieValueRegex) {
  235. if (x.test(cookieValue)) {
  236. debugger;
  237. }
  238. }
  239. }
  240.  
  241. function now() {
  242. // 东八区专属...
  243. return "[" + new Date(new Date().getTime() + 1000 * 60 * 60 * 8).toJSON().replace("T", " ").replace("Z", "") + "] ";
  244. }
  245.  
  246. function genFormatArray(messageAndStyleArray) {
  247. const formatArray = [];
  248. for (let i = 0, end = messageAndStyleArray.length / 2; i < end; i++) {
  249. formatArray.push("%c%s");
  250. }
  251. return formatArray.join("");
  252. }
  253.  
  254. function getCodeLocation() {
  255. const callstack = new Error().stack.split("\n");
  256. while (callstack.length && callstack[0].indexOf("cc11001100") === -1) {
  257. callstack.shift();
  258. }
  259. callstack.shift();
  260. callstack.shift();
  261.  
  262. return callstack[0].trim();
  263. }
  264.  
  265. /**
  266. * 将本次设置cookie的字符串解析为容易处理的形式
  267. *
  268. * @param cookieString
  269. * @returns {CookiePair}
  270. */
  271. function parseSetCookie(cookieString) {
  272. // uuid_tt_dd=10_37476713480-1609821005397-659114; Expires=Thu, 01 Jan 1025 00:00:00 GMT; Path=/; Domain=.csdn.net;
  273. const cookieStringSplit = cookieString.split(";");
  274. const cookieNameValueArray = cookieStringSplit[0].split("=", 2);
  275. const cookieName = decodeURIComponent(cookieNameValueArray[0].trim());
  276. const cookieValue = cookieNameValueArray.length > 1 ? decodeURIComponent(cookieNameValueArray[1].trim()) : "";
  277. const map = new Map();
  278. for (let i = 1; i < cookieStringSplit.length; i++) {
  279. const ss = cookieStringSplit[i].split("=", 2);
  280. const key = ss[0].trim().toLowerCase();
  281. const value = ss.length > 1 ? ss[1].trim() : "";
  282. map.set(key, value);
  283. }
  284. // 当不设置expires的时候关闭浏览器就过期
  285. const expires = map.get("expires") || 0;
  286. return new CookiePair(cookieName, cookieValue, new Date(expires).getTime())
  287. }
  288.  
  289. /**
  290. * 获取当前所有已经设置的cookie
  291. *
  292. * @returns {Map<string, CookiePair>}
  293. */
  294. function getCurrentCookieMap() {
  295. const cookieMap = new Map();
  296. if (!document.cookie) {
  297. return cookieMap;
  298. }
  299. document.cookie.split(";").forEach(x => {
  300. const ss = x.split("=", 2);
  301. const key = decodeURIComponent(ss[0].trim());
  302. const value = ss.length > 1 ? decodeURIComponent(ss[1].trim()) : "";
  303. cookieMap.set(key, new CookiePair(key, value));
  304. });
  305. return cookieMap;
  306. }
  307.  
  308. class CookiePair {
  309. constructor(name, value, expires) {
  310. this.name = name;
  311. this.value = value;
  312. this.expires = expires;
  313. }
  314. }
  315.  
  316. })();