├── CustomRules.js ├── README.md ├── assets └── screenshot │ ├── screenshot01.png │ └── screenshot02.png └── doc ├── FiddlerScript_api_reference.md ├── Fiddler教程.md └── img ├── UI_base.png ├── downloadFiddler01.png ├── downloadFiddler_dec01.png ├── downloadFiddler_dec02.png ├── downloadFiddler_dec03.png └── pic001.png /CustomRules.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Fiddler CustomRules 3 | * @Version 2.2.2 4 | * @Author xxxily 5 | * @home https://github.com/xxxily/Fiddler-plus 6 | * @bugs https://github.com/xxxily/Fiddler-plus/issues 7 | */ 8 | 9 | /** 10 | * 全局配置项 11 | * 可配置链接类型的颜色,代理、替换地址等, 12 | * 默认对象的键【key】为要匹配的规则,值【key】为匹配后的配置 13 | */ 14 | var GLOBAL_SETTING: Object = { 15 | // 全局开启禁止缓存功能【不建议开启,严重影响上网体验】 16 | disableCaching: false, 17 | // 自定义禁止缓存列表【针对性禁止缓存,进行项目调试的时候不影响其它网站的上网体验】 18 | disableCachingList: [ 19 | "xxxily.com.cn|xxxily.net.cn", 20 | // 禁止本地所有连接的缓存,包括IP为192段或127段的所有地址 21 | // "http://localhost|http://192|http://127", 22 | // "https://localhost|https://192|https://127" 23 | ], 24 | // 过滤配置【用于过滤出哪些URL需要显示,哪些需要隐藏】 25 | Filter: { 26 | // 只显示URL包含以下字符的连接 27 | showLinks:[ 28 | // "qq.com", 29 | // "baidu.com" 30 | ], 31 | // 不能直接吧 :443规则写在 hideLinks 过滤项上,否则大部分的无关链接都会被间接隐藏 32 | // Tunnel To 影响前端审查,隐藏掉,目前无法彻底隐藏,逻辑待优化 33 | hideTunnelTo:true, 34 | // 隐藏URL包含以下字符串的连接 过滤 35 | hideLinks: [ 36 | /*隐藏静态资源*/ 37 | // ".jpg|.jpeg|.png|.gif", 38 | // "baidu.com|qzone.qq.com|qq.com", 39 | /*隐藏热更新时的请求*/ 40 | // "browser-sync|sockjs-node", 41 | // "192.168", 42 | // "hm.baidu.com", 43 | // "google.com|googleapis.com|mail.google|www.google", 44 | ], 45 | // 头部字段过滤器,除非您很熟悉或想测试下这部分功能,否则不建议您使用该过滤项 46 | headerFieldFilter: [ 47 | { 48 | //可选值 Referer Content-Type|Cookie|User-Agent|Accept-Language|Host 等,只要是头部支持的值都可以 49 | fieldName:'Referer', 50 | workAt:'request', // request|response 51 | display:true, 52 | filterList:[ 53 | 'xxxily.cc' 54 | ], 55 | enabled: false 56 | } 57 | ], 58 | // 只显示以下文件类型【注意:是根据header的 Content-Type字段进行匹配的,所以js文件直接写js是不行的,但支持模糊匹配 】 59 | // 附注:使用ContentType过滤的时候不一定准确,不带 ContentType的连接会被自动隐藏,该过滤选项的逻辑还有待优化和完善 60 | showContentType: [ 61 | // "image" 62 | // "css", 63 | // "html", 64 | // "javascript" 65 | ], 66 | // 隐藏以下文件类型 67 | hideContentType: [ 68 | // "image" 69 | // "css", 70 | // "html", 71 | // "javascript" 72 | ] 73 | }, 74 | // 替换URL【可用于多环境切换、解决跨域、快速调试线上脚本等】 75 | replace:{ 76 | "http://xxxily.com/":"http://xxxily.cc/", 77 | /*替换成本地某个对应目录下的文件*/ 78 | "http://xxxily.com/m":"D:\\work\\" 79 | }, 80 | // 替换URL的高级版,可以实现多个项目区分管理,进行二级匹配等 81 | replacePlus:[ 82 | { 83 | describe:"将【xxxily】服务器上的静态资源替换成本地服务器上的资源", 84 | source:[ 85 | "http://xxxily.net", 86 | "http://xxxily.org", 87 | "http://xxxily.ac.cn", 88 | "http://xxxily.cc" 89 | ], 90 | /*Referer限定,方便精确控制【注意:Referer限定会导致只能在当前限定页面下查看后续链接,否则会不能正常代理】*/ 91 | Referer:[ 92 | '\\w*.html' 93 | ], 94 | subRules:[ 95 | { 96 | describe:"subRules 字段跟父级字段完全一致,主要是方便对特殊情况进行单独处理" 97 | } 98 | ], 99 | urlContain:"\\.html|\\.css|\\.js|\\.jpeg|\\.jpg|\\.png|\\.gif|\\.mp4|\\.flv|\\.webp", 100 | replaceWith:"http://localhost:3000", 101 | enabled:false 102 | }, 103 | { 104 | describe:"将【本地】请求的接口替换成某个服务器上的接口内容", 105 | source:[ 106 | "http://localhost:3000/", 107 | "http://127.0.0.1:3000/", 108 | "http://192.168.0.101:3000/" 109 | ], 110 | urlContain:"", 111 | urlUnContain:"\\.html|\\.css|\\.js|\\.jpeg|\\.jpg|\\.png|\\.gif|\\.ico|\\.mp4|\\.flv|\\.webp|/browser-sync/", 112 | // bgColor:"#2c2c2c", 113 | color:"#FF0000", 114 | // bold:"true", 115 | replaceWith:"http://xxxily.cc/", 116 | enabled:false 117 | } 118 | ], 119 | 120 | // 脚本注入 121 | scriptInject:[ 122 | { 123 | describe:"脚本注入使用示例", 124 | // 要注入的脚本路径,可以是本地目录下的脚本,也可以是线上URL脚本 125 | scriptPath:"D:\\work\\debugTools\\commonInject.js", 126 | // 指定脚本要放置在哪个dom标签里面,默认html 可选值有:html,body,head,title 127 | tagName:"head", 128 | // 指定放置在标签的哪个位置,默认是before 可选值有 before,after 129 | // position:'after', 130 | /*禁止注入脚本的缓存,也就是为scriptPath增加时间戳参数,默认true*/ 131 | noCaching:true, 132 | /*条件限定*/ 133 | urlContain:[], 134 | urlUnContain:[], 135 | enabled: false 136 | } 137 | ], 138 | 139 | // callbackAcion 主要用于自由度更高得自定义操作,通常是为了实现某些特定的功能 140 | // 例如修改某个接口的数据,从而跳过界面的操作的设定值,进行某些功能的破解或自动化操作 141 | // 注意:如果匹配的链接过多,很容易导致:数组下标超限/未将对象应用设置到对象实例等错误弹窗提示 142 | callbackAcion:[ 143 | { 144 | describe: "回调操作示例代码", 145 | source:[ 146 | 'http://xxxily.cc/dispather-app/dispacher\\?method=dispacher' 147 | ], 148 | // exclude:[], 149 | include:[ 150 | '.html', 151 | '.jsp' 152 | ], 153 | // 可选值有:OnBeforeRequest OnPeekAtResponseHeaders OnBeforeResponse OnDone OnReturningError ,想匹配多个事件可以使用|进行分隔 154 | onEvent:'OnBeforeRequest', 155 | callback:function(oSession,eventName){ 156 | var t = this; 157 | console.log(eventName); 158 | 159 | if(eventName === 'OnBeforeRequest'){ 160 | var Cookie = oSession.oRequest['Cookie']; 161 | if(Cookie){ 162 | console.log(Cookie); 163 | }else { 164 | console.log('没找到对应的 Cookie'); 165 | } 166 | console.log('callbackTest:',oSession.fullUrl); 167 | oSession.oRequest['Cookie'] = "aaa"; 168 | } 169 | 170 | }, 171 | enabled: false 172 | }, 173 | { 174 | describe: "篡改登录信息示例", 175 | source:[ 176 | 'https://xxxily.cc/portal/userLoginAction!checkUser.action' 177 | ], 178 | onEvent:'OnBeforeRequest', 179 | callback:function(oSession,eventName){ 180 | var webForms = oSession.GetRequestBodyAsString(), 181 | strConv = coreApi.strConv, 182 | webFormsObj = strConv.parse(webForms); 183 | 184 | webFormsObj['username'] = "testUser"; 185 | webFormsObj['password'] = "testPw"; 186 | 187 | /*重设请求参数*/ 188 | oSession.utilSetRequestBody(strConv.stringify(webFormsObj)); 189 | }, 190 | enabled: false 191 | }, 192 | { 193 | describe: "本地脚本注入示例", 194 | source:[ 195 | "xxxily.net.cn", 196 | "xxxily.com.cn" 197 | ], 198 | include:[ 199 | '.html', 200 | '.jsp', 201 | 'vendor.js', 202 | 'commonInjectForDebug' 203 | ], 204 | onEvent:'OnBeforeResponse', 205 | callback:function(oSession,eventName){ 206 | 207 | /*给HTML页面注入调试脚本*/ 208 | if ( oSession.oResponse.headers.ExistsAndContains("Content-Type", "text/html") && oSession.utilFindInResponse("", false)>-1 ){ 209 | 210 | oSession.utilDecodeResponse(); 211 | var oBody = System.Text.Encoding.UTF8.GetString(oSession.responseBodyBytes); 212 | 213 | /*注入到head标签之前*/ 214 | var oRegEx = //i, 215 | scriptList = [ 216 | '', 217 | '\n' 218 | ]; 219 | oBody = oBody.replace(oRegEx, scriptList.join('')); 220 | 221 | oSession.utilSetResponseBody(oBody); 222 | } 223 | 224 | /*将注入的脚本地址内容替换成本地文件,实现本地脚本内容注入*/ 225 | if( oSession.fullUrl.indexOf('commonInjectForDebug') > -1 ){ 226 | oSession["x-replywithfile"] ="D:\\work\\debugTools\\commonInject.js"; 227 | } 228 | 229 | }, 230 | enabled: true 231 | } 232 | ], 233 | 234 | // 进行字符串查找,如果查找到将在Log面板显示查找结果 235 | Search: { 236 | inRequestHeaders: [], 237 | inResponseHeaders: [], 238 | inResponseBody: [] 239 | }, 240 | 241 | // 界面显示配置【可以对不同链接进行颜色标识,以便快速定位相关链接】 242 | UI: { 243 | // 默认文本颜色 244 | color: "#c0c0c0",//灰白色 245 | // 默认背景颜色 246 | bgColor: "#2c2c2c",//浅黑 247 | bgColor_02: "#2f2f2f",//浅黑【用于做交替背景,可以不设置】 248 | // bgColor_02:"#4b4a4a", 249 | // 链接返回报错时的颜色 250 | onError: { 251 | // bgColor:"#2c2c2c", 252 | color: "#FF0000" //错误红 253 | // ,bold:"true" 254 | }, 255 | // 不同关键词匹配对应的连接颜色,key 对应的是匹配的关键字,val对应的是匹配的颜色 256 | linkColor: { 257 | "^http": "#ccccff", 258 | "^https": "#ccffff", 259 | "\\.jpg|\\.png|\\.gif": "#ffccff", //粉紫色 260 | "\\.js": "#00ff00", //原谅色 261 | "\\.css": "#ffcc66", //米黄 262 | "\\.html": "#00d7ff", //蓝色 263 | "\\.php": "#fff32d", //大黄 264 | "\\.jsp": "#fd4107" //砖红 265 | }, 266 | // 根据 contentType 匹配连接颜色 267 | contentTypeColor: { 268 | "image": "#ffccff", 269 | "css": "#ffcc66", 270 | // html 类型容易跟其他接口数据混淆,无特别明确情况下不建议对其进行特殊标识 271 | // "html":"#00d7ff", 272 | "javascript": "#00ff00" 273 | }, 274 | // 可以为特殊状态码设置不同颜色,方便快速定位一些错误链接,例如404等 275 | // 注意:这个只是根据responseCode 来匹配的,一些不存在response的链接配置是无效的,例如 502,504状态,应该是在onError里配置的 276 | statusCode: { 277 | "404|408|500|502|504": "#FF0000", //错误红 278 | "304": "#5e5e5e" //浅灰色 279 | }, 280 | // 高亮,对特殊的链接进行高亮设置,方便跟踪查看链接 281 | highlight: { 282 | /* 283 | ".action": { 284 | color: "#FF0000", //警告红 285 | describe: "标红接口,好快速定位接口连接" 286 | }, 287 | */ 288 | /* 289 | "positionAjax.json": { 290 | color: "#FF0000", //警告红 291 | describe: "标红接口,好快速定位接口连接" 292 | }, 293 | */ 294 | /* 295 | "http://localhost|192.168":{ 296 | // bgColor:"#2c2c2c", //浅黑 297 | color:"#00ff00", //原谅色 298 | bold:"true", 299 | describe:"高亮测试" 300 | }, 301 | "youdao.com":{ 302 | bgColor:"#FF0000", //红色 303 | color:"#fdf404", //黄色 304 | bold:"true", 305 | describe:"高亮测试" 306 | }, 307 | "google.com|googleapis.com":{ 308 | bgColor:"#00ff00", //原谅色 309 | color:"#fdf404", //黄色 310 | bold:"true", 311 | describe:"高亮测试" 312 | }, 313 | */ 314 | "": "" 315 | } 316 | }, 317 | // 一些实用工具集,先列个可能会开发的工具集,留个坑以后有时间再开发 318 | Tools: { 319 | // TODO API 测试工具 320 | apiTest: {}, 321 | // TODO 重放攻击工具> 322 | replay: {}, 323 | // TODO 内容注入工具 324 | contentInject: {}, 325 | // TODO 类似 weinre 这样的注入调试工具 326 | weinre: {} 327 | }, 328 | // 多项分隔符号【同一个配置需匹配多项规则时可以通过分隔符进行区分,这样就不用每个规则都要新开一份配置那么繁琐】 329 | splitStr: "|", 330 | // 正则匹配的修饰符:i,g,m 默认i,不区分大小写 331 | regAttr: "i" 332 | }; 333 | //全局配置项 END 334 | 335 | // 调试方法 BEGIN 336 | if (!console) { 337 | var console = {}; 338 | console.log = function (arg1, arg2, arg3, arg4, arg5, arg6) { 339 | // 不支持 arguments ,尴尬! 340 | var args = [arg1, arg2, arg3, arg4, arg5, arg6]; 341 | var argsLen = 0; 342 | for (var i = 0; i < args.length; i++) { 343 | var arg = args[i]; 344 | if (typeof arg === "undefined") { 345 | break; 346 | } 347 | argsLen += 1; 348 | 349 | var argType = typeof arg; 350 | 351 | if (argType === "string" || argType === "number") { 352 | FiddlerObject.log(arg); 353 | } else if (argType === "boolean") { 354 | FiddlerObject.log("boolean:" + arg); 355 | } else if (argType === "object" && arg.toString) { 356 | FiddlerObject.log(arg.toString()); 357 | } else { 358 | try { 359 | FiddlerObject.log("尝试遍历输出:" + argType); 360 | for (var str = "" in arg) { 361 | FiddlerObject.log(str + ":" + arg[str]); 362 | } 363 | } catch (ex) { 364 | FiddlerObject.log("遍历输出失败:" + ex); 365 | FiddlerObject.log(arg); 366 | } 367 | } 368 | } 369 | if (argsLen > 1) { 370 | FiddlerObject.log("----------------------------------------------"); 371 | } 372 | } 373 | } 374 | if (!alert) { 375 | var alert = FiddlerObject.alert; 376 | } 377 | // 调试方法 END 378 | 379 | /*核心API 内置一些常用的方法 BEGIN*/ 380 | var coreApi: Object = { 381 | /*字符串转换器*/ 382 | strConv:{ 383 | /** 384 | * 把具有某些规则的字符串转换为对象字面量,例如:a=1&b=2&c=3 385 | * @param regularStr (String) -必选,某个规则的字符串 386 | * #param splitStr (String) -可选 分隔符,默认& 387 | */ 388 | parse:function parse (regularStr,splitStr) { 389 | var str = regularStr || '', 390 | splitStr = splitStr || '&', 391 | obj = {}, 392 | arr = str.split(splitStr), 393 | len = arr.length; 394 | 395 | for(var i = 0 ; i < len ; i++){ 396 | var curArr = arr[i].split('='); 397 | obj[curArr[0]] = curArr[1]; 398 | } 399 | if(!str){ 400 | obj = {} ; 401 | } 402 | return obj ; 403 | }, 404 | /** 405 | * 把使用上面的parse方法转换的对象,重新转换成字符串表达形式 406 | * @param obj (object) -必选,某个对象 407 | * #param splitStr (String) -可选 分隔符,默认& 408 | */ 409 | stringify:function (obj,splitStr) { 410 | if(!obj){ 411 | return ; 412 | } 413 | var splitStr = splitStr || '&', 414 | strArr = [] ; 415 | for (var key in obj) { 416 | var val = obj[key], 417 | valType = typeof val ; 418 | if( (valType === 'string' || valType === 'number') && val != "" ){ 419 | strArr.push(key+'='+val); 420 | }else if(valType === 'object'){ 421 | /*二级对象是不支持的,为了不报错,将二级对象使用[object]进行代替*/ 422 | strArr.push(key+'='+'[object]'); 423 | } 424 | } 425 | return strArr.join(splitStr); 426 | } 427 | } 428 | }; 429 | /*核心常用API 内置一些常用的方法 END*/ 430 | 431 | /** 432 | * 自动移除禁止项,减少后续逻辑不必要的循环消耗 433 | * @param obj 要操作的对象 434 | */ 435 | function removeDisableItem(source) { 436 | var me = this, 437 | result = {} 438 | ; 439 | if(typeof source != 'object'){ 440 | return source; 441 | } 442 | if (Object.prototype.toString.call(source) === '[object Array]') { 443 | result = []; 444 | } 445 | if (Object.prototype.toString.call(source) === '[object Null]') { 446 | result = null; 447 | } 448 | for (var key in source) { 449 | if(typeof source[key] === 'object'){ 450 | /*跳过enabled为false的项目*/ 451 | if(Object.prototype.toString.call(source[key]) === '[object Object]' && source[key]['enabled'] === false){ 452 | continue; 453 | }else { 454 | result[key] = removeDisableItem(source[key]) 455 | } 456 | }else{ 457 | result[key] = source[key] 458 | } 459 | } 460 | return result; 461 | } 462 | 463 | // 实测 GLOBAL_SETTING 每个请求都要加载一次,所以此优化可能出现反效果,后续如果出现很多enabled为false的配置再开启实测下 464 | // GLOBAL_SETTING = removeDisableItem(GLOBAL_SETTING); 465 | 466 | 467 | // .NET Framework API document 468 | // https://docs.microsoft.com/zh-cn/dotnet/api/index?view=netframework-4.7.2 469 | import System; 470 | import System.Windows.Forms; 471 | import Fiddler; 472 | // INTRODUCTION 473 | // 474 | // Well, hello there! 475 | // 476 | // Don't be scared! :-) 477 | // 478 | // This is the FiddlerScript Rules file, which creates some of the menu commands and 479 | // other features of Fiddler. You can edit this file to modify or add new commands. 480 | // 481 | // The original version of this file is named SampleRules.js and it is in the 482 | // \Program Files\Fiddler\ folder. When Fiddler first runs, it creates a copy named 483 | // CustomRules.js inside your \Documents\Fiddler2\Scripts folder. If you make a 484 | // mistake in editing this file, simply delete the CustomRules.js file and restart 485 | // Fiddler. A fresh copy of the default rules will be created from the original 486 | // sample rules file. 487 | 488 | // The best way to edit this file is to install the FiddlerScript Editor, part of 489 | // the free SyntaxEditing addons. Get it here: http://fiddler2.com/r/?SYNTAXVIEWINSTALL 490 | 491 | // GLOBALIZATION NOTE: Save this file using UTF-8 Encoding. 492 | 493 | // JScript.NET Reference 494 | // http://fiddler2.com/r/?msdnjsnet 495 | // 496 | // FiddlerScript Reference 497 | // http://fiddler2.com/r/?fiddlerscriptcookbook 498 | 499 | class Handlers { 500 | // ***************** 501 | // 502 | // This is the Handlers class. Pretty much everything you ever add to FiddlerScript 503 | // belongs right inside here, or inside one of the already-existing functions below. 504 | // 505 | // ***************** 506 | 507 | // The following snippet demonstrates a custom-bound column for the Web Sessions list. 508 | // See http://fiddler2.com/r/?fiddlercolumns for more info 509 | /* 510 | public static BindUIColumn("Method", 60) 511 | function FillMethodColumn(oS: Session): String { 512 | return oS.RequestMethod; 513 | } 514 | */ 515 | 516 | // The following snippet demonstrates how to create a custom tab that shows simple text 517 | /* 518 | public BindUITab("Flags") 519 | static function FlagsReport(arrSess: Session[]):String { 520 | var oSB: System.Text.StringBuilder = new System.Text.StringBuilder(); 521 | for (var i:int = 0; i 0){ 844 | settingUnMatch(fullUrl, contain, function (matchStr) { 845 | isPass = false; 846 | }, "【isPassUrlRestriction里面的urlContain】配置出错,请检查你的配置"); 847 | } 848 | 849 | // urlUnContain限定 850 | if(isPass && unContain && unContain.length > 0){ 851 | settingMatch(fullUrl, unContain, function (matchStr) { 852 | isPass = false; 853 | }, "【isPassUrlRestriction里面的urlContain】配置出错,请检查你的配置"); 854 | } 855 | return isPass; 856 | } 857 | 858 | /** 859 | * 判断某个字符串是否为本地路径 860 | */ 861 | public static function isLocalPath(pathStr) { 862 | return /^[a-zA-Z]:\\\w*/.test(pathStr) 863 | } 864 | 865 | /** 866 | * 把链接地址统一成数组形式 867 | * @param links (string|array) -必选 链接地址 868 | */ 869 | public static function linksToArr(links) { 870 | var arr = []; 871 | if(links && typeof links === 'string'){ 872 | arr.push(links); 873 | }else if(Object.prototype.toString.call(links) === '[object Array]'){ 874 | arr = links; 875 | } 876 | return arr; 877 | } 878 | 879 | /** 880 | * 跟进分割字符串提取后面的路径片段 881 | * @param fullUrl (string) -必选 完整的url地址 882 | * @param splitStr (string) -必选 分割字符串 883 | */ 884 | public static function extractPathSection(fullUrl,splitStr) { 885 | return fullUrl.replace(splitStr,'|cutoff|').replace(/^.*\|cutoff\|/,''); 886 | } 887 | 888 | /** 889 | * 进行本地路径拼接 890 | * @param localPath (string) -必选 本地路径(起始段) 891 | * @param pathSection (string) -必选 需拼接起来的路径片段 892 | */ 893 | public static function joinLocalPath(localPath,pathSection) { 894 | var path = localPath, 895 | section = pathSection; 896 | /*确保path结尾为\*/ 897 | if(!/\\$/.test(path)){ 898 | path+='\\' 899 | } 900 | /*将/转为\并且删除?或#后面的所有字符串*/ 901 | section = section.replace(/\//g,'\\').replace(/[\?#].*$/,''); 902 | /*确保section起始不包含\*/ 903 | if(/^\\/.test(section)){ 904 | section = section.replace(/^\\/,''); 905 | } 906 | 907 | var realPath = (path+section).replace(/\\$/,''); 908 | return realPath; 909 | } 910 | 911 | /** 912 | * 提取需要展示的连接,由于每个连接都要重新提取和进行匹配计算,所以会造成较大的性能开销 913 | * 此功能处于测试阶段,如果出现较多报错建议 将 m_off_promiscuousMode 设为true 914 | */ 915 | public static function extractShowLinks() { 916 | var showLinks = linksToArr(GLOBAL_SETTING.Filter.showLinks); 917 | 918 | /** 919 | * 使用混杂模式的话,自动提取各个选项需要展示的URL地址 920 | * 由于不进行重复性判断,所以可能会出错和造成性能下降 921 | * */ 922 | if(!m_off_promiscuousMode && !m_off_allPlusRules){ 923 | /*提取replace里面要显示的连接*/ 924 | for (var key in GLOBAL_SETTING.replace) { 925 | if(key){ 926 | showLinks.push(key); 927 | } 928 | } 929 | 930 | /*提取replacePlus里面要显示的连接*/ 931 | for (var i=0; i < GLOBAL_SETTING.replacePlus.length; i++) { 932 | var item = GLOBAL_SETTING.replacePlus[i]; 933 | if(item.enabled && item.source){ 934 | showLinks = showLinks.concat(linksToArr(item.source)); 935 | } 936 | } 937 | 938 | /*提取 scriptInject 里面要显示的连接*/ 939 | var hasInjectLocalFile = false; 940 | for (var i=0; i < GLOBAL_SETTING.scriptInject.length; i++) { 941 | var item = GLOBAL_SETTING.scriptInject[i]; 942 | if(item.enabled && item.scriptPath){ 943 | if(isLocalPath(item.scriptPath)){ 944 | hasInjectLocalFile = true; 945 | }else { 946 | showLinks.push(item.scriptPath); 947 | } 948 | if(item.urlContain){ 949 | showLinks = showLinks.concat(linksToArr(item.urlContain)); 950 | } 951 | } 952 | } 953 | if(hasInjectLocalFile){ 954 | showLinks.push('locationScriptInjectByFiddlerForDebug.js'); 955 | } 956 | 957 | /*提取callbackAcion里面要显示的连接*/ 958 | for (var i=0; i < GLOBAL_SETTING.callbackAcion.length; i++) { 959 | var item = GLOBAL_SETTING.callbackAcion[i]; 960 | if(item.enabled && item.source){ 961 | showLinks = showLinks.concat(linksToArr(item.source)); 962 | } 963 | } 964 | 965 | /*提取UI.highlight里面要显示的连接*/ 966 | for (var key in GLOBAL_SETTING.UI.highlight) { 967 | if(key){ 968 | showLinks.push(key); 969 | } 970 | } 971 | } 972 | 973 | return showLinks; 974 | } 975 | 976 | /** 977 | * 脚本注入器 978 | * @param oSession (session) -必选 session对象 979 | */ 980 | public static function scriptInjecter(oSession) { 981 | if(m_off_injectRules || m_off_allPlusRules){ 982 | return false; 983 | } 984 | 985 | var scriptInject = GLOBAL_SETTING.scriptInject; 986 | if(scriptInject && scriptInject.length > 0){ 987 | var len = scriptInject.length; 988 | for (var i = 0; i < len; i++) { 989 | var settingItem = scriptInject[i]; 990 | if (settingItem.enabled === true && settingItem.scriptPath) { 991 | 992 | /*将注入的脚本地址内容替换成本地文件,实现本地脚本内容注入*/ 993 | if( oSession.fullUrl.indexOf('locationScriptInjectByFiddlerForDebug') > -1 && isLocalPath(settingItem.scriptPath)){ 994 | oSession["x-replywithfile"] = settingItem.scriptPath; 995 | }else { 996 | var isHtmlType = oSession.oResponse.headers.ExistsAndContains("Content-Type", "text/html"), 997 | isPass = isPassUrlRestriction(oSession.fullUrl,settingItem.urlContain || [],settingItem.urlUnContain || []);; 998 | 999 | if(isHtmlType && isPass){ 1000 | oSession.utilDecodeResponse(); 1001 | /*通过了检测,但内容不包含正确的html内容,也不能进行插入操作*/ 1002 | if(oSession.utilFindInResponse("", false)>-1){ 1003 | var oBody = System.Text.Encoding.UTF8.GetString(oSession.responseBodyBytes); 1004 | 1005 | var beginStr = settingItem.position === 'after' ? '', 'i'), 1007 | timestamp = settingItem.noCaching === false ? "" : '?injectForDebug=' + new Date().getTime(), 1008 | injectTagStr = ''; 1009 | 1010 | if(isLocalPath(settingItem.scriptPath)){ 1011 | injectTagStr = ''; 1012 | } 1013 | 1014 | var matchStr = oBody.match(oRegEx); 1015 | if(matchStr){ 1016 | matchStr = matchStr[0]; 1017 | } 1018 | 1019 | var injectStr = injectTagStr+'\n' + matchStr ; 1020 | if(settingItem.position === 'after'){ 1021 | injectStr = matchStr + '\n' + injectTagStr; 1022 | } 1023 | 1024 | oBody = oBody.replace(oRegEx, injectStr); 1025 | oSession.utilSetResponseBody(oBody); 1026 | } 1027 | } 1028 | } 1029 | } 1030 | } 1031 | } 1032 | } 1033 | 1034 | /** 1035 | * 简单代理替换 1036 | * @param oSession (session) -必选 session对象 1037 | */ 1038 | public static function replaceAgency(oSession) { 1039 | if(m_off_replaceRules || m_off_allPlusRules){ 1040 | return false; 1041 | } 1042 | 1043 | // 简单替换 1044 | settingMatch(oSession.fullUrl, GLOBAL_SETTING.replace, function (conf, matchStr) { 1045 | // System.Text.RegularExpressions.Regex.IsMatch(oSession.fullUrl, "https://" ); 1046 | if(isLocalPath(conf)){ 1047 | //进行本地文件替换 1048 | var pathSection = extractPathSection(oSession.fullUrl,matchStr); 1049 | var locPath = joinLocalPath(conf,pathSection); 1050 | oSession["x-replywithfile"] = locPath; 1051 | console.log('文件替换成功:',oSession.fullUrl + '\n的内容被替换成了如下本地文件的内容:\n' + locPath); 1052 | }else { 1053 | /*进行url地址替换*/ 1054 | oSession.fullUrl = System.Text.RegularExpressions.Regex.Replace(oSession.fullUrl, matchStr, conf); 1055 | } 1056 | }, "【replace】配置出错,请检查你的配置"); 1057 | } 1058 | 1059 | /** 1060 | * 高级代理替换 1061 | * @param oSession (session) -必选 session对象 1062 | */ 1063 | public static function replacePlusAgency(oSession) { 1064 | if(m_off_replaceRules || m_off_allPlusRules){ 1065 | return false; 1066 | } 1067 | 1068 | // 执行替换规则 1069 | function replaceWithRule(rule) { 1070 | if (rule.enabled !== false && !oSession["has-replace"]) { 1071 | settingMatch(oSession.fullUrl, rule.source, function (conf, matchStr) { 1072 | var hasPassCheck = true ; 1073 | 1074 | // Referer限定 1075 | if(hasPassCheck && rule.Referer && rule.Referer.length > 0){ 1076 | 1077 | if(!oSession.oRequest['Referer']){ 1078 | hasPassCheck = false; 1079 | } 1080 | 1081 | settingUnMatch(oSession.oRequest['Referer'], rule.Referer, function (matchStr02) { 1082 | hasPassCheck = false; 1083 | }, "【replacePlus里面的Referer】配置出错,请检查你的配置"); 1084 | 1085 | /*请求页面通过限定,才能往下玩*/ 1086 | settingMatch(oSession.fullUrl, rule.Referer, function (matchStr02) { 1087 | hasPassCheck = true; 1088 | }, "【replacePlus里面的Referer】配置出错,请检查你的配置"); 1089 | 1090 | } 1091 | 1092 | // url的contain、unContain限定 1093 | if(hasPassCheck){ 1094 | hasPassCheck = isPassUrlRestriction(oSession.fullUrl,rule.urlContain || [],rule.urlUnContain || []); 1095 | } 1096 | 1097 | // 禁止缓存 1098 | if(hasPassCheck && typeof rule.disableCaching === 'boolean'){ 1099 | oSession["disableCaching"] = rule.disableCaching; 1100 | } 1101 | 1102 | // 如果存在子级规则,则进行子级递归替换(理论可以上可以进行无限级的递归) 1103 | if(hasPassCheck && rule.subRules){ 1104 | for (var i=0; i < rule.subRules.length; i++) { 1105 | var subRules = rule.subRules[i]; 1106 | replaceWithRule(subRules); 1107 | } 1108 | } 1109 | 1110 | // 执行替换操作 1111 | if(hasPassCheck && !oSession["has-replace"] && rule.replaceWith){ 1112 | if( isLocalPath(rule.replaceWith) ){ 1113 | var pathSection = extractPathSection(oSession.fullUrl,matchStr); 1114 | var locPath = joinLocalPath(rule.replaceWith,pathSection); 1115 | oSession["x-replywithfile"] = locPath; 1116 | console.log('文件替换成功:',oSession.fullUrl + '\n的内容被替换成了如下本地文件的内容:\n' + locPath); 1117 | }else { 1118 | var newUrl = System.Text.RegularExpressions.Regex.Replace(oSession.fullUrl, matchStr, rule.replaceWith); 1119 | oSession.fullUrl = newUrl; 1120 | } 1121 | 1122 | setSessionDisplay(oSession, rule); 1123 | 1124 | // 标注已被替换过 防多次替换,也意味着子级的替换优先级高于父级的 1125 | oSession["has-replace"] = true; 1126 | } 1127 | 1128 | }, "【replacePlus】配置出错,请检查你的配置"); 1129 | } 1130 | } 1131 | 1132 | // 高级替换 1133 | var replacePlus = GLOBAL_SETTING.replacePlus; 1134 | if (replacePlus && replacePlus.length > 0) { 1135 | var len = replacePlus.length; 1136 | for (var i = 0; i < len; i++) { 1137 | var rule = replacePlus[i]; 1138 | replaceWithRule(rule); 1139 | } 1140 | } 1141 | } 1142 | 1143 | // 用于背景做交替显示的记号 1144 | public static var showLinkCount = 0; 1145 | 1146 | /** 1147 | * 隐藏连接,为了确保程序没错误隐藏连接,固写成统一的方法,方便快速调试 1148 | * @param oSession (Session) -必选,Session 对象 1149 | */ 1150 | public static function hideLink(oSession) { 1151 | // console.log("以下连接已被隐藏:",oSession.fullUrl); 1152 | showLinkCount -= 1; 1153 | oSession["ui-hide"] = "true"; 1154 | oSession.Ignore(); 1155 | } 1156 | 1157 | /** 1158 | * 隐藏连接,TunnelTo 一堆无用连接 1159 | * @param oSession (Session) -必选,Session 对象 1160 | */ 1161 | public static function hideTunnelToLink(oSession) { 1162 | // TODO 过滤 tunnel to 连接 待优化 1163 | var hideTunnelTo = GLOBAL_SETTING.Filter.hideTunnelTo; 1164 | if(hideTunnelTo && !m_off_tunnelTo && !m_off_allPlusRules){ 1165 | settingMatch(oSession.fullUrl, [':443'], function () { 1166 | hideLink(oSession); 1167 | }, "【hideTunnelTo】配置出错,请检查你的配置"); 1168 | } 1169 | } 1170 | 1171 | /** 1172 | * 每个匹配链接的回调操作对象 1173 | * @param oSession (Session) -必选,Session 对象 1174 | * @param eventName (String) -必选,回调事件名称 可选值有:OnBeforeRequest OnPeekAtResponseHeaders OnBeforeResponse OnDone OnReturningError 1175 | */ 1176 | public static function sessionCallback(oSession,eventName) { 1177 | if(m_off_allPlusRules || m_off_callbackAcion || !oSession || !eventName || !settingMatch){ 1178 | // console.log('出现【未将对象引用设置到对象实例】的错误~'); 1179 | return false; 1180 | } 1181 | 1182 | var callbackAcion = GLOBAL_SETTING.callbackAcion; 1183 | if (callbackAcion && callbackAcion.length > 0) { 1184 | var len = callbackAcion.length; 1185 | for (var i = 0; i < len; i++) { 1186 | var settingItem = callbackAcion[i]; 1187 | 1188 | /*指定要匹配的事件,减少不必要的回调操作*/ 1189 | var eventPatt = createPattern(settingItem['onEvent'],'i'); 1190 | if (settingItem.enabled && eventPatt.test(eventName)) { 1191 | settingMatch(oSession.fullUrl, settingItem.source, function (conf, matchStr) { 1192 | 1193 | if(settingItem.include){ 1194 | settingMatch(oSession.fullUrl,settingItem.include,function (conf,matchStr) { 1195 | settingItem.callback(oSession,eventName); 1196 | },"【callbackAcion_include】配置出错,请检查你的配置") 1197 | }else if(settingItem.exclude) { 1198 | settingUnMatch(oSession.fullUrl,settingItem.include,function (matchStr) { 1199 | settingItem.callback(oSession,eventName); 1200 | },"【callbackAcion_exclude】配置出错,请检查你的配置") 1201 | }else { 1202 | settingItem.callback(oSession,eventName); 1203 | } 1204 | 1205 | return true; 1206 | }, "【callbackAcion】配置出错,请检查你的配置"); 1207 | } 1208 | } 1209 | } 1210 | } 1211 | 1212 | /** 1213 | * 设置 Session 的界面呈现 1214 | * @param oSession (Session) -必选,Session 对象 1215 | * @param conf (Object) -可选,要设置 Session 呈现的界面配置,形如:{bgColor:"#2c2c2c",color:"#FF0000",bold:true} 1216 | */ 1217 | public static function setSessionDisplay(oSession, conf) { 1218 | conf.bgColor ? oSession["ui-backcolor"] = conf.bgColor : ""; 1219 | conf.color ? oSession["ui-color"] = conf.color : ""; 1220 | conf.bold ? oSession["ui-bold"] = "true" : ""; 1221 | } 1222 | 1223 | public static function oSessionCore(oSession, opt) { 1224 | if (!oSession) { 1225 | return false; 1226 | } 1227 | // 核心方法 1228 | var core = { 1229 | /** 1230 | * 设置 Session 的界面呈现 1231 | * @param oSession (Session) -必选,Session 对象 1232 | * @param conf (Object) -可选,要设置 Session 呈现的界面配置,形如:{bgColor:"#2c2c2c",color:"#FF0000",bold:true} 1233 | */ 1234 | setDisplay: function (conf) { 1235 | conf.bgColor ? oSession["ui-backcolor"] = conf.bgColor : ""; 1236 | conf.color ? oSession["ui-color"] = conf.color : ""; 1237 | conf.bold ? oSession["ui-bold"] = "true" : ""; 1238 | } 1239 | }; 1240 | // if(opt instanceof Object && opt["run"] && core[] ) 1241 | } 1242 | 1243 | // ------------ 通用公共方法 END ------------ 1244 | 1245 | // 参考文档 http://docs.telerik.com/fiddler/KnowledgeBase/FiddlerScript/ModifyRequestOrResponse 1246 | static function OnBeforeRequest(oSession: Session) { 1247 | 1248 | showLinkCount += 1; 1249 | 1250 | /*Fiddler有时候会掉链子,所以必须加强判断,才能减少callbackAcion功能的报错提示*/ 1251 | if(settingMatch && settingUnMatch && oSession){ 1252 | sessionCallback && sessionCallback(oSession,'OnBeforeRequest'); 1253 | 1254 | if(!m_off_filterRules && !m_off_allPlusRules){ 1255 | // 过滤出需要显示或隐藏的连接 BEGIN 1256 | 1257 | var showLinks = extractShowLinks(), 1258 | hideLinks = GLOBAL_SETTING.Filter.hideLinks; 1259 | // 过滤出要显示的连接,把不在显示列表里的连接隐藏掉 1260 | settingUnMatch(oSession.fullUrl, showLinks, function () { 1261 | hideLink(oSession); 1262 | }, "【showLinks】配置出错,请检查你的配置"); 1263 | 1264 | // 过滤出要隐藏的连接,把在隐藏列表里的连接隐藏掉 1265 | settingMatch(oSession.fullUrl, hideLinks, function (conf, matchStr) { 1266 | hideLink(oSession); 1267 | return true; 1268 | }, "【hideLinks】配置出错,请检查你的配置"); 1269 | 1270 | // 过滤出需要显示或隐藏的连接 END 1271 | 1272 | // 根据头部字段过滤出需要显示或隐藏的连接 BEGIN 1273 | var headerFilter = GLOBAL_SETTING.Filter.headerFieldFilter; 1274 | if (headerFilter && headerFilter.length > 0) { 1275 | var hfLen = headerFilter.length; 1276 | for (var i = 0; i < hfLen; i++) { 1277 | var settingItem = headerFilter[i]; 1278 | if (settingItem.enabled === true && settingItem.workAt === 'request' && settingItem.fieldName) { 1279 | if(settingItem.display === true ){ 1280 | // 过滤出要显示的连接,把不在显示列表里的连接隐藏掉 1281 | if(oSession.oRequest[settingItem.fieldName]){ 1282 | settingUnMatch(oSession.oRequest[settingItem.fieldName], settingItem.filterList, function () { 1283 | hideLink(oSession); 1284 | }, "【headerFieldFilter_show】配置出错,请检查你的配置"); 1285 | }else { 1286 | /*不存在对于header属值的,不能直接隐藏,只能后续操作,否则会把其关联的链接也一并隐藏,最后就会导致无任何链接可显示*/ 1287 | // hideLink(oSession); 1288 | // console.log('以下链接本该隐藏的,但是由于具有关联性,不能将其马上隐藏:',oSession.fullUrl); 1289 | oSession['hide-me'] = 'true'; 1290 | } 1291 | }else { 1292 | // 过滤出要隐藏的连接,把在隐藏列表里的连接隐藏掉 1293 | settingMatch(oSession.oRequest[settingItem.fieldName], settingItem.filterList, function (conf, matchStr) { 1294 | hideLink(oSession); 1295 | return true; 1296 | }, "【headerFieldFilter_hide】配置出错,请检查你的配置"); 1297 | } 1298 | } 1299 | } 1300 | } 1301 | // 根据头部字段过滤出需要显示或隐藏的连接 END 1302 | } 1303 | 1304 | // 过滤出要禁止缓存的连接 1305 | var disableCachingList = GLOBAL_SETTING.disableCachingList; 1306 | settingMatch(oSession.fullUrl, disableCachingList, function (conf, matchStr) { 1307 | oSession["disableCaching"] = true; 1308 | return true; 1309 | }, "【disableCachingList】配置出错,请检查你的配置"); 1310 | 1311 | 1312 | // 标注隐藏443链接 1313 | if(GLOBAL_SETTING.Filter.hideTunnelTo && !m_off_tunnelTo && !m_off_allPlusRules){ 1314 | settingMatch(oSession.fullUrl, [':443'], function () { 1315 | oSession['hide-me'] = 'true'; 1316 | }, "【hideTunnelTo】配置出错,请检查你的配置"); 1317 | } 1318 | 1319 | // 配色 BEGIN 1320 | 1321 | if (!oSession["ui-hide"] && GLOBAL_SETTING.UI) { 1322 | // 默认背景【存在 bgColor_02 时进行交替显示】 注:因为http请求的无序特性,所以不能确保百分百准确交替,待深入优化 1323 | if (GLOBAL_SETTING.UI.bgColor_02 && (showLinkCount % 2 === 0)) { 1324 | oSession["ui-backcolor"] = GLOBAL_SETTING.UI.bgColor_02; 1325 | } else { 1326 | oSession["ui-backcolor"] = GLOBAL_SETTING.UI.bgColor || "#2c2c2c"; 1327 | } 1328 | 1329 | // 默认文本颜色 1330 | oSession["ui-color"] = GLOBAL_SETTING.UI.color; 1331 | 1332 | // 根据关键词设置连接渲染的颜色 1333 | settingMatch(oSession.fullUrl, GLOBAL_SETTING.UI.linkColor, function (conf, matchStr) { 1334 | conf ? oSession["ui-color"] = conf : ""; 1335 | }, "【linkColor】配置出错,请检查你的配置"); 1336 | } 1337 | 1338 | if (!oSession["ui-hide"] && GLOBAL_SETTING.skin) { 1339 | var skins = GLOBAL_SETTING.skin, 1340 | skLen = skins.length; 1341 | for (var i = 0; i < skLen; i++) { 1342 | var skin = skins[i]; 1343 | if (skin.enable === true) { 1344 | // 1345 | } 1346 | } 1347 | } 1348 | 1349 | // 配色 END 1350 | 1351 | // 接管替换URL BEGIN 1352 | replaceAgency(oSession); 1353 | replacePlusAgency(oSession); 1354 | // 接管替换URL END 1355 | 1356 | // 根据关键字进行搜索查找 BEGIN 1357 | var searchInRequestHeaders = GLOBAL_SETTING.Search.inRequestHeaders, 1358 | searchStrCount = searchInRequestHeaders.length; 1359 | for (var i = 0; i < searchStrCount; i++) { 1360 | var skw = searchInRequestHeaders[i]; 1361 | try { 1362 | var reqHeaders = oSession.RequestHeaders; 1363 | if (reqHeaders) { 1364 | for (var key = "" in reqHeaders) { 1365 | 1366 | var result01 = isMatch(reqHeaders[key], skw); 1367 | var result02 = isMatch(key, skw); 1368 | if (result01 || result02) { 1369 | console.log(oSession.fullUrl, "进行RequestHeaders搜索查找时找到匹配的字符串:" + skw, key + ":" + reqHeaders[key]); 1370 | } 1371 | } 1372 | } 1373 | } catch (ex) { 1374 | console.log("进行RequestHeaders遍历搜索时出错,当前搜索关键词为:" + skw); 1375 | } 1376 | } 1377 | // 根据关键字进行搜索查找 END 1378 | } 1379 | 1380 | // Sample Rule: Color ASPX requests in RED 1381 | // if (oSession.uriContains(".aspx")) { oSession["ui-color"] = "red"; } 1382 | 1383 | // Sample Rule: Flag POSTs to fiddler2.com in italics 1384 | // if (oSession.HostnameIs("www.fiddler2.com") && oSession.HTTPMethodIs("POST")) { oSession["ui-italic"] = "yup"; } 1385 | 1386 | // Sample Rule: Break requests for URLs containing "/sandbox/" 1387 | // if (oSession.uriContains("/sandbox/")) { 1388 | // oSession.oFlags["x-breakrequest"] = "yup"; // Existence of the x-breakrequest flag creates a breakpoint; the "yup" value is unimportant. 1389 | // } 1390 | 1391 | // 通过QuickExec 输入字符串来筛选出要高亮的url 1392 | if ((null != filter_and_highlight_url) && oSession.uriContains(filter_and_highlight_url)) { 1393 | oSession["ui-color"] = "#FF0000"; 1394 | oSession["ui-bold"] = "true"; 1395 | } 1396 | 1397 | if ((null != gs_ReplaceToken) && (oSession.fullUrl.indexOf(gs_ReplaceToken) > -1)) { // Case sensitive 1398 | oSession.fullUrl = oSession.fullUrl.Replace(gs_ReplaceToken, gs_ReplaceTokenWith); 1399 | } 1400 | if ((null != gs_OverridenHost) && (oSession.host.toLowerCase() == gs_OverridenHost)) { 1401 | oSession["x-overridehost"] = gs_OverrideHostWith; 1402 | } 1403 | 1404 | if ((null != bpRequestURI) && oSession.uriContains(bpRequestURI)) { 1405 | oSession["x-breakrequest"] = "uri"; 1406 | } 1407 | 1408 | if ((null != bpMethod) && (oSession.HTTPMethodIs(bpMethod))) { 1409 | oSession["x-breakrequest"] = "method"; 1410 | } 1411 | 1412 | if ((null != uiBoldURI) && oSession.uriContains(uiBoldURI)) { 1413 | oSession["ui-bold"] = "QuickExec"; 1414 | } 1415 | 1416 | if (m_SimulateModem) { 1417 | // Delay sends by 300ms per KB uploaded. 1418 | oSession["request-trickle-delay"] = "300"; 1419 | // Delay receives by 150ms per KB downloaded. 1420 | oSession["response-trickle-delay"] = "150"; 1421 | } 1422 | 1423 | if (m_DisableCaching || GLOBAL_SETTING.disableCaching || oSession["disableCaching"]) { 1424 | oSession.oRequest.headers.Remove("If-None-Match"); 1425 | oSession.oRequest.headers.Remove("If-Modified-Since"); 1426 | oSession.oRequest["Pragma"] = "no-cache"; 1427 | } 1428 | 1429 | // User-Agent Overrides 1430 | if (null != sUA) { 1431 | oSession.oRequest["User-Agent"] = sUA; 1432 | } 1433 | 1434 | if (m_Japanese) { 1435 | oSession.oRequest["Accept-Language"] = "ja"; 1436 | } 1437 | 1438 | if (m_AutoAuth) { 1439 | // Automatically respond to any authentication challenges using the 1440 | // current Fiddler user's credentials. You can change (default) 1441 | // to a domain\\username:password string if preferred. 1442 | // 1443 | // WARNING: This setting poses a security risk if remote 1444 | // connections are permitted! 1445 | oSession["X-AutoAuth"] = "(default)"; 1446 | } 1447 | 1448 | if (m_AlwaysFresh && (oSession.oRequest.headers.Exists("If-Modified-Since") || oSession.oRequest.headers.Exists("If-None-Match"))) { 1449 | oSession.utilCreateResponseAndBypassServer(); 1450 | oSession.responseCode = 304; 1451 | oSession["ui-backcolor"] = "Lavender"; 1452 | } 1453 | } 1454 | 1455 | //OnBeforeRequest END 1456 | 1457 | // This function is called immediately after a set of request headers has 1458 | // been read from the client. This is typically too early to do much useful 1459 | // work, since the body hasn't yet been read, but sometimes it may be useful. 1460 | // 1461 | // For instance, see 1462 | // http://blogs.msdn.com/b/fiddler/archive/2011/11/05/http-expect-continue-delays-transmitting-post-bodies-by-up-to-350-milliseconds.aspx 1463 | // for one useful thing you can do with this handler. 1464 | // 1465 | // Note: oSession.requestBodyBytes is not available within this function! 1466 | /* 1467 | static function OnPeekAtRequestHeaders(oSession: Session) { 1468 | var sProc = ("" + oSession["x-ProcessInfo"]).ToLower(); 1469 | if (!sProc.StartsWith("mylowercaseappname")) oSession["ui-hide"] = "NotMyApp"; 1470 | } 1471 | */ 1472 | 1473 | // 1474 | // If a given session has response streaming enabled, then the OnBeforeResponse function 1475 | // is actually called AFTER the response was returned to the client. 1476 | // 1477 | // In contrast, this OnPeekAtResponseHeaders function is called before the response headers are 1478 | // sent to the client (and before the body is read from the server). Hence this is an opportune time 1479 | // to disable streaming (oSession.bBufferResponse = true) if there is something in the response headers 1480 | // which suggests that tampering with the response body is necessary. 1481 | // 1482 | // Note: oSession.responseBodyBytes is not available within this function! 1483 | // 1484 | static function OnPeekAtResponseHeaders(oSession: Session) { 1485 | 1486 | if(settingMatch && settingUnMatch && oSession){ 1487 | sessionCallback && sessionCallback(oSession,'OnPeekAtResponseHeaders'); 1488 | } 1489 | 1490 | //FiddlerApplication.Log.LogFormat("Session {0}: Response header peek shows status is {1}", oSession.id, oSession.responseCode); 1491 | if (m_DisableCaching || GLOBAL_SETTING.disableCaching || oSession["disableCaching"]) { 1492 | oSession.oResponse.headers.Remove("Expires"); 1493 | oSession.oResponse["Cache-Control"] = "no-cache"; 1494 | } 1495 | 1496 | if ((bpStatus > 0) && (oSession.responseCode == bpStatus)) { 1497 | oSession["x-breakresponse"] = "status"; 1498 | oSession.bBufferResponse = true; 1499 | } 1500 | 1501 | if ((null != bpResponseURI) && oSession.uriContains(bpResponseURI)) { 1502 | oSession["x-breakresponse"] = "uri"; 1503 | oSession.bBufferResponse = true; 1504 | } 1505 | 1506 | } 1507 | 1508 | static function OnBeforeResponse(oSession: Session) { 1509 | 1510 | if(settingMatch && settingUnMatch && oSession){ 1511 | sessionCallback && sessionCallback(oSession,'OnBeforeResponse'); 1512 | 1513 | var contentType = oSession.oResponse["Content-Type"]; 1514 | 1515 | if(!m_off_filterRules){ 1516 | // 根据头部字段过滤出需要显示或隐藏的连接 BEGIN 1517 | var headerFilter = GLOBAL_SETTING.Filter.headerFieldFilter; 1518 | if (headerFilter && headerFilter.length > 0) { 1519 | var hfLen = headerFilter.length; 1520 | for (var i = 0; i < hfLen; i++) { 1521 | var settingItem = headerFilter[i]; 1522 | if (settingItem.enabled === true && settingItem.workAt === 'response' && settingItem.fieldName) { 1523 | if(settingItem.display === true ){ 1524 | // 过滤出要显示的连接,把不在显示列表里的连接隐藏掉 1525 | if(oSession.oRequest[settingItem.fieldName]){ 1526 | settingUnMatch(oSession.oResponse[settingItem.fieldName], settingItem.filterList, function () { 1527 | hideLink(oSession); 1528 | }, "【headerFieldFilter_show】配置出错,请检查你的配置"); 1529 | }else { 1530 | /*不存在对于header属值的,不能直接隐藏,只能后续操作,否则会把其关联的链接也一并隐藏,最后就会导致无任何链接可显示*/ 1531 | // hideLink(oSession); 1532 | // console.log('以下链接本该隐藏的,但是由于具有关联性,不能将其马上隐藏:',oSession.fullUrl); 1533 | oSession['hide-me'] = 'true'; 1534 | } 1535 | }else { 1536 | // 过滤出要隐藏的连接,把在隐藏列表里的连接隐藏掉 1537 | settingMatch(oSession.oResponse[settingItem.fieldName], settingItem.filterList, function (conf, matchStr) { 1538 | hideLink(oSession); 1539 | return true; 1540 | }, "【headerFieldFilter_hide】配置出错,请检查你的配置"); 1541 | } 1542 | } 1543 | } 1544 | } 1545 | // 根据头部字段过滤出需要显示或隐藏的连接 END 1546 | 1547 | // 过滤出需要显示或隐藏的连接 BEGIN 1548 | var showContentType = GLOBAL_SETTING.Filter.showContentType, 1549 | hideContentType = GLOBAL_SETTING.Filter.hideContentType; 1550 | 1551 | //开启了 ContentType 过滤的时候, 把不带 Content-Type 全部过滤掉 1552 | if (!contentType && showContentType.length > 0) { 1553 | console.log("隐藏不带 ContentType 的连接"); 1554 | hideLink(oSession); 1555 | } 1556 | 1557 | // 过滤出要显示的连接,把不在显示列表里的连接隐藏掉 1558 | settingUnMatch(contentType, showContentType, function () { 1559 | hideLink(oSession); 1560 | }, "【showContentType】配置出错,请检查你的配置"); 1561 | 1562 | // 过滤出要隐藏的连接,把在隐藏列表里的连接隐藏掉 1563 | settingMatch(contentType, hideContentType, function (conf, matchStr) { 1564 | hideLink(oSession); 1565 | return true; 1566 | }, "【hideContentType】配置出错,请检查你的配置"); 1567 | 1568 | // 过滤出需要显示或隐藏的连接 END 1569 | } 1570 | 1571 | // 根据contentType显示连接颜色 1572 | var contentTypeColor = GLOBAL_SETTING.UI.contentTypeColor; 1573 | if (oSession.responseCode > 100 && oSession.responseCode < 300) { 1574 | settingMatch(contentType, contentTypeColor, function (conf, matchStr) { 1575 | conf ? oSession["ui-color"] = conf : ""; 1576 | return true; 1577 | }, "【contentTypeColor】配置出错,请检查你的配置"); 1578 | } 1579 | 1580 | // 根据不同状态码设置链接颜色 1581 | var statusCode = GLOBAL_SETTING.UI.statusCode; 1582 | settingMatch(oSession.responseCode, statusCode, function (conf, matchStr) { 1583 | oSession["ui-color"] = GLOBAL_SETTING.UI.color; 1584 | conf ? oSession["ui-color"] = conf : ""; 1585 | return true; 1586 | }, "【statusCode】配置出错,请检查你的配置"); 1587 | 1588 | scriptInjecter(oSession); 1589 | 1590 | // 高亮特殊连接 1591 | settingMatch(oSession.fullUrl, GLOBAL_SETTING.UI.highlight, function (conf, matchStr) { 1592 | setSessionDisplay(oSession, conf); 1593 | }, "【highlight】配置出错,请检查你的配置"); 1594 | } 1595 | 1596 | if (m_Hide304s && oSession.responseCode == 304) { 1597 | oSession["ui-hide"] = "true"; 1598 | } 1599 | 1600 | } 1601 | 1602 | //OnBeforeResponse END 1603 | 1604 | // 请求完成时的回调 1605 | static function OnDone(oSession: Session) { 1606 | 1607 | if(settingMatch && settingUnMatch && oSession){ 1608 | sessionCallback && sessionCallback(oSession,'OnDone'); 1609 | 1610 | // 隐藏被标注要隐藏的链接 1611 | if(oSession['hide-me']){ 1612 | hideLink(oSession); 1613 | } 1614 | 1615 | // 根据关键字进行搜索查找 BEGIN 1616 | 1617 | // 查找ResponseHeaders 1618 | var searchInResponseHeaders = GLOBAL_SETTING.Search.inResponseHeaders, 1619 | searchStrCount = searchInResponseHeaders.length; 1620 | for (var i = 0; i < searchStrCount; i++) { 1621 | var skw = searchInResponseHeaders[i]; 1622 | try { 1623 | var resHeaders = oSession.ResponseHeaders; 1624 | if (resHeaders) { 1625 | for (var key = "" in resHeaders) { 1626 | 1627 | var result01 = isMatch(resHeaders[key], skw); 1628 | var result02 = isMatch(key, skw); 1629 | if (result01 || result02) { 1630 | console.log(oSession.fullUrl, "进行ResponseHeaders搜索查找时找到匹配的字符串:" + skw, key + ":" + resHeaders[key]); 1631 | } 1632 | } 1633 | } 1634 | } catch (ex) { 1635 | console.log("进行ResponseHeaders遍历搜索时出错,当前搜索关键词为:" + skw); 1636 | } 1637 | } 1638 | 1639 | // 查找ResponseBody 1640 | var searchInResponseBody = GLOBAL_SETTING.Search.inResponseBody, 1641 | searchStrCount = searchInResponseBody.length; 1642 | if (searchStrCount > 0) { 1643 | var resBody = oSession.GetResponseBodyAsString(); 1644 | for (var i = 0; i < searchStrCount; i++) { 1645 | var skw = searchInResponseBody[i]; 1646 | try { 1647 | if (isMatch(resBody, skw)) { 1648 | console.log(oSession.fullUrl, "进行ResponseBody搜索查找时找到匹配的字符串:" + skw); 1649 | } 1650 | } catch (ex) { 1651 | console.log("进行ResponseBody遍历搜索时出错,当前搜索关键词为:" + skw); 1652 | } 1653 | } 1654 | } 1655 | 1656 | // 根据关键字进行搜索查找 END 1657 | } 1658 | 1659 | // 通过QuickExec 输入字符串来筛选出要高亮的url 1660 | if ((null != filter_and_highlight_url) && isMatch(oSession.GetResponseBodyAsString(), filter_and_highlight_url)) { 1661 | oSession["ui-color"] = "#FF0000"; 1662 | oSession["ui-bold"] = "false"; 1663 | console.log(oSession.fullUrl, "发现匹配的内容:", filter_and_highlight_url); 1664 | } 1665 | } 1666 | 1667 | /** 1668 | * 链接返回出错时的回调方法 1669 | */ 1670 | static function OnReturningError(oSession: Session) { 1671 | 1672 | if(settingMatch && settingUnMatch && oSession){ 1673 | sessionCallback && sessionCallback(oSession,'OnReturningError'); 1674 | 1675 | hideTunnelToLink(oSession); 1676 | } 1677 | 1678 | // 出错时的颜色配置 1679 | var onErrorConf = GLOBAL_SETTING.UI.onError; 1680 | !onErrorConf.bgColor ? onErrorConf.bgColor = GLOBAL_SETTING.UI.bgColor : ""; 1681 | setSessionDisplay(oSession, onErrorConf); 1682 | } 1683 | 1684 | /* 1685 | // This function executes just before Fiddler returns an error that it has 1686 | // itself generated (e.g. "DNS Lookup failure") to the client application. 1687 | // These responses will not run through the OnBeforeResponse function above. 1688 | static function OnReturningError(oSession: Session) { 1689 | } 1690 | */ 1691 | /* 1692 | // This function executes after Fiddler finishes processing a Session, regardless 1693 | // of whether it succeeded or failed. Note that this typically runs AFTER the last 1694 | // update of the Web Sessions UI listitem, so you must manually refresh the Session's 1695 | // UI if you intend to change it. 1696 | static function OnDone(oSession: Session) { 1697 | } 1698 | */ 1699 | 1700 | /* 1701 | static function OnBoot() { 1702 | MessageBox.Show("Fiddler has finished booting"); 1703 | System.Diagnostics.Process.Start("iexplore.exe"); 1704 | 1705 | UI.ActivateRequestInspector("HEADERS"); 1706 | UI.ActivateResponseInspector("HEADERS"); 1707 | } 1708 | */ 1709 | 1710 | /* 1711 | static function OnBeforeShutdown(): Boolean { 1712 | // Return false to cancel shutdown. 1713 | return ((0 == FiddlerApplication.UI.lvSessions.TotalItemCount()) || 1714 | (DialogResult.Yes == MessageBox.Show("Allow Fiddler to exit?", "Go Bye-bye?", 1715 | MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2))); 1716 | } 1717 | */ 1718 | 1719 | /* 1720 | static function OnShutdown() { 1721 | MessageBox.Show("Fiddler has shutdown"); 1722 | } 1723 | */ 1724 | 1725 | /* 1726 | static function OnAttach() { 1727 | MessageBox.Show("Fiddler is now the system proxy"); 1728 | } 1729 | */ 1730 | 1731 | /* 1732 | static function OnDetach() { 1733 | MessageBox.Show("Fiddler is no longer the system proxy"); 1734 | } 1735 | */ 1736 | 1737 | // The Main() function runs everytime your FiddlerScript compiles 1738 | static function Main() { 1739 | 1740 | var today: Date = new Date(); 1741 | FiddlerObject.StatusText = " CustomRules.js was loaded at: " + today; 1742 | 1743 | // Uncomment to add a "Server" column containing the response "Server" header, if present 1744 | // UI.lvSessions.AddBoundColumn("Server", 50, "@response.server"); 1745 | 1746 | // Uncomment to add a global hotkey (Win+G) that invokes the ExecAction method below... 1747 | // UI.RegisterCustomHotkey(HotkeyModifiers.Windows, Keys.G, "screenshot"); 1748 | } 1749 | 1750 | // These static variables are used for simple breakpointing & other QuickExec rules 1751 | BindPref("fiddlerscript.ephemeral.bpRequestURI") 1752 | public static var bpRequestURI: String = null; 1753 | 1754 | BindPref("fiddlerscript.ephemeral.bpResponseURI") 1755 | public static var bpResponseURI: String = null; 1756 | 1757 | BindPref("fiddlerscript.ephemeral.bpMethod") 1758 | public static var bpMethod: String = null; 1759 | 1760 | static var bpStatus: int = -1; 1761 | static var uiBoldURI: String = null; 1762 | static var gs_ReplaceToken: String = null; 1763 | static var gs_ReplaceTokenWith: String = null; 1764 | static var gs_OverridenHost: String = null; 1765 | static var gs_OverrideHostWith: String = null; 1766 | static var filter_and_highlight_url: String = null; //根据匹配的字符来高亮筛选出的url 1767 | 1768 | // The OnExecAction function is called by either the QuickExec box in the Fiddler window, 1769 | // or by the ExecAction.exe command line utility. 1770 | static function OnExecAction(sParams: String[]): Boolean { 1771 | 1772 | FiddlerObject.StatusText = "ExecAction: " + sParams[0]; 1773 | 1774 | var sAction = sParams[0].toLowerCase(); 1775 | switch (sAction) { 1776 | case "bold": 1777 | if (sParams.Length < 2) { 1778 | uiBoldURI = null; 1779 | FiddlerObject.StatusText = "Bolding cleared"; 1780 | return false; 1781 | } 1782 | uiBoldURI = sParams[1]; 1783 | FiddlerObject.StatusText = "Bolding requests for " + uiBoldURI; 1784 | return true; 1785 | case "bp": 1786 | FiddlerObject.alert("bpu = breakpoint request for uri\nbpm = breakpoint request method\nbps=breakpoint response status\nbpafter = breakpoint response for URI"); 1787 | return true; 1788 | case "bps": 1789 | if (sParams.Length < 2) { 1790 | bpStatus = -1; 1791 | FiddlerObject.StatusText = "Response Status breakpoint cleared"; 1792 | return false; 1793 | } 1794 | bpStatus = parseInt(sParams[1]); 1795 | FiddlerObject.StatusText = "Response status breakpoint for " + sParams[1]; 1796 | return true; 1797 | case "bpv": 1798 | case "bpm": 1799 | if (sParams.Length < 2) { 1800 | bpMethod = null; 1801 | FiddlerObject.StatusText = "Request Method breakpoint cleared"; 1802 | return false; 1803 | } 1804 | bpMethod = sParams[1].toUpperCase(); 1805 | FiddlerObject.StatusText = "Request Method breakpoint for " + bpMethod; 1806 | return true; 1807 | case "bpu": 1808 | if (sParams.Length < 2) { 1809 | bpRequestURI = null; 1810 | FiddlerObject.StatusText = "RequestURI breakpoint cleared"; 1811 | return false; 1812 | } 1813 | bpRequestURI = sParams[1]; 1814 | FiddlerObject.StatusText = "RequestURI breakpoint for " + sParams[1]; 1815 | return true; 1816 | case "bpa": 1817 | case "bpafter": 1818 | if (sParams.Length < 2) { 1819 | bpResponseURI = null; 1820 | FiddlerObject.StatusText = "ResponseURI breakpoint cleared"; 1821 | return false; 1822 | } 1823 | bpResponseURI = sParams[1]; 1824 | FiddlerObject.StatusText = "ResponseURI breakpoint for " + sParams[1]; 1825 | return true; 1826 | case "overridehost": 1827 | if (sParams.Length < 3) { 1828 | gs_OverridenHost = null; 1829 | FiddlerObject.StatusText = "Host Override cleared"; 1830 | return false; 1831 | } 1832 | gs_OverridenHost = sParams[1].toLowerCase(); 1833 | gs_OverrideHostWith = sParams[2]; 1834 | FiddlerObject.StatusText = "Connecting to [" + gs_OverrideHostWith + "] for requests to [" + gs_OverridenHost + "]"; 1835 | return true; 1836 | case "urlreplace": 1837 | if (sParams.Length < 3) { 1838 | gs_ReplaceToken = null; 1839 | FiddlerObject.StatusText = "URL Replacement cleared"; 1840 | return false; 1841 | } 1842 | gs_ReplaceToken = sParams[1]; 1843 | gs_ReplaceTokenWith = sParams[2].Replace(" ", "%20"); // Simple helper 1844 | FiddlerObject.StatusText = "Replacing [" + gs_ReplaceToken + "] in URIs with [" + gs_ReplaceTokenWith + "]"; 1845 | return true; 1846 | case "allbut": 1847 | case "keeponly": 1848 | if (sParams.Length < 2) { 1849 | FiddlerObject.StatusText = "Please specify Content-Type to retain during wipe."; 1850 | return false; 1851 | } 1852 | UI.actSelectSessionsWithResponseHeaderValue("Content-Type", sParams[1]); 1853 | UI.actRemoveUnselectedSessions(); 1854 | UI.lvSessions.SelectedItems.Clear(); 1855 | FiddlerObject.StatusText = "Removed all but Content-Type: " + sParams[1]; 1856 | return true; 1857 | case "stop": 1858 | UI.actDetachProxy(); 1859 | return true; 1860 | case "start": 1861 | UI.actAttachProxy(); 1862 | return true; 1863 | case "cls": 1864 | case "clear": 1865 | UI.actRemoveAllSessions(); 1866 | return true; 1867 | case "g": 1868 | case "go": 1869 | UI.actResumeAllSessions(); 1870 | return true; 1871 | case "goto": 1872 | if (sParams.Length != 2) return false; 1873 | Utilities.LaunchHyperlink("http://www.google.com/search?hl=en&btnI=I%27m+Feeling+Lucky&q=" + Utilities.UrlEncode(sParams[1])); 1874 | return true; 1875 | case "help": 1876 | Utilities.LaunchHyperlink("http://fiddler2.com/r/?quickexec"); 1877 | return true; 1878 | case "hide": 1879 | UI.actMinimizeToTray(); 1880 | return true; 1881 | case "log": 1882 | FiddlerApplication.Log.LogString((sParams.Length < 2) ? "User couldn't think of anything to say..." : sParams[1]); 1883 | return true; 1884 | case "nuke": 1885 | UI.actClearWinINETCache(); 1886 | UI.actClearWinINETCookies(); 1887 | return true; 1888 | case "screenshot": 1889 | UI.actCaptureScreenshot(false); 1890 | return true; 1891 | case "show": 1892 | UI.actRestoreWindow(); 1893 | return true; 1894 | case "tail": 1895 | if (sParams.Length < 2) { 1896 | FiddlerObject.StatusText = "Please specify # of sessions to trim the session list to."; 1897 | return false; 1898 | } 1899 | UI.TrimSessionList(int.Parse(sParams[1])); 1900 | return true; 1901 | case "quit": 1902 | UI.actExit(); 1903 | return true; 1904 | case "dump": 1905 | UI.actSelectAll(); 1906 | UI.actSaveSessionsToZip(CONFIG.GetPath("Captures") + "dump.saz"); 1907 | UI.actRemoveAllSessions(); 1908 | FiddlerObject.StatusText = "Dumped all sessions to " + CONFIG.GetPath("Captures") + "dump.saz"; 1909 | return true; 1910 | 1911 | default: 1912 | if (sAction.StartsWith("http") || sAction.StartsWith("www.")) { 1913 | System.Diagnostics.Process.Start(sParams[0]); 1914 | return true; 1915 | } else if (sParams[0] === "*") { 1916 | filter_and_highlight_url = null; 1917 | FiddlerObject.StatusText = "取消URL高亮"; 1918 | } else { 1919 | filter_and_highlight_url = sParams[0]; 1920 | FiddlerObject.StatusText = "将为你高亮包含【" + filter_and_highlight_url + "】的url"; 1921 | // FiddlerObject.StatusText = "Requested ExecAction: '" + sAction + "' not found. Type HELP to learn more."; 1922 | return true; 1923 | } 1924 | } 1925 | } 1926 | } 1927 | 1928 | 1929 | 1930 | 1931 | 1932 | 1933 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fiddler plus 【高效调试分析利器】 2 | 3 | Fiddler是一个功能强大的HTTP抓包调试工具!但用起来却不是那么的顺手,界面繁杂、配置复杂,非常不符合高效程序猿的使用习惯... 4 | Fiddler plus 重新定义了Fiddler的CustomRules.js,从而使Fiddler拥有了更加简单、灵活、丰富的配置方式,高逼格的显示界面,让你用起来不再羞(gan)涩(ga)。 5 | 6 | ## 开发进度说明: 7 | ~~最后面提到的开发计划,现已基本开发完,但最近在写说明文档,所以还需过一段时间才会发布新版本~~ 8 |
9 | ~~ps:文档比代码难写多了!有木有。。。~~ 10 | 11 | 12 | ## 特点 13 | * 自定义皮肤,通过简单配置即可拥有跟你喜爱的编辑器一样的界面风格 14 | * 高亮特殊链接,一眼便可定位后台接口、快速区分前端各类静态资源 15 | * 快速切换运行环境,无需重启、即刻生效,环境再多也不凌乱 16 | * 简单配置即可彻底解决跨域开发的窘境 17 | * 强大的过滤功能,轻松过滤无关链接 18 | 19 | 20 | ## 优点&目标 21 | 功能强大、配置简单 22 | 23 | ## 界面截图 24 | ![运行截图](https://raw.githubusercontent.com/xxxily/Fiddler-plus/master/assets/screenshot/screenshot01.png "运行截图") 25 | 26 | ![运行截图](https://raw.githubusercontent.com/xxxily/Fiddler-plus/master/assets/screenshot/screenshot02.png "运行截图") 27 | 28 | ## 使用方式 29 | 30 | 下载当前的 CustomRules.js ,替换掉Fiddler自带的 CustomRules.js 。 31 | 正常情况下文件的所在目录为: 32 | 33 | %USERPROFILE%\Documents\Fiddler2\Scripts 34 | 35 | 你也可以通过Fiddle菜单栏下的 Rules》Customize Rules... 即可打开编辑CustomRules 36 | 37 | ## 全局配置项一览 38 | ```javascript 39 | /** 40 | * 全局配置项 41 | * 可配置链接类型的颜色,代理、替换地址等, 42 | * 默认对象的键【key】为要匹配的规则,值【key】为匹配后的配置 43 | */ 44 | var GLOBAL_SETTING:Object = { 45 | // 开启或禁止缓存 46 | disableCaching:true, 47 | // 过滤配置【用于过滤出哪些URL需要显示,哪些需要隐藏】 48 | Filter:{ 49 | // 只显示URL包含以下字符的连接 50 | showLinks:[ 51 | // "qq.com", 52 | // "baidu.com", 53 | // "youdao.com" 54 | ], 55 | // 不能直接吧 :443规则写在 hideLinks 过滤项上,否则大部分的无关链接都会被间接隐藏 56 | // Tunnel To 影响前端审查,隐藏掉,目前无法彻底隐藏,逻辑待优化 57 | hideTunnelTo:true, 58 | // 隐藏URL包含以下字符串的连接 过滤 59 | hideLinks:[ 60 | // "baidu.com|qzone.qq.com|qq.com", 61 | "hm.baidu.com", 62 | "google.com|googleapis.com" 63 | ], 64 | // 只显示以下文件类型【注意:是根据header的 Content-Type字段进行匹配的,所以js文件直接写js是不行的,但支持模糊匹配 】 65 | // 附注:使用ContentType过滤的时候不一定准确,不带 ContentType的连接会被自动隐藏,该过滤选项的逻辑还有待优化和完善 66 | showContentType:[ 67 | // "image" 68 | // "css", 69 | // "html", 70 | // "javascript" 71 | ], 72 | // 隐藏以下文件类型 73 | hideContentType:[] 74 | }, 75 | // 替换URL【可用于多环境切换、解决跨域、快速调试线上脚本等】 76 | replace:{ 77 | "http://xxxily.com/":"http://xxxily.cc/", 78 | /*替换成本地某个对应目录下的文件*/ 79 | "http://xxxily.com/m":"D:\\work\\" 80 | }, 81 | // 替换URL的高级版,可以实现多个项目区分管理,进行二级匹配等 82 | replacePlus:[ 83 | { 84 | describe:"将【xxxily】服务器上的静态资源替换成本地服务器上的资源", 85 | source:[ 86 | "http://xxxily.net", 87 | "http://xxxily.org", 88 | "http://xxxily.ac.cn", 89 | "http://xxxily.cc" 90 | ], 91 | /*Referer限定,方便精确控制*/ 92 | Referer:[ 93 | '\\w*.html' 94 | ], 95 | subRules:[ 96 | { 97 | describe:"subRules 字段跟父级字段完全一致,主要是方便对特殊情况进行单独处理" 98 | } 99 | ], 100 | urlContain:"\\.html|\\.css|\\.js|\\.jpeg|\\.jpg|\\.png|\\.gif|\\.mp4|\\.flv|\\.webp", 101 | replaceWith:"http://localhost:3000", 102 | enabled:false 103 | }, 104 | { 105 | describe:"将【本地】请求的接口替换成某个服务器上的接口内容", 106 | source:[ 107 | "http://localhost:3000/", 108 | "http://127.0.0.1:3000/", 109 | "http://192.168.0.101:3000/" 110 | ], 111 | urlContain:"", 112 | urlUnContain:"\\.html|\\.css|\\.js|\\.jpeg|\\.jpg|\\.png|\\.gif|\\.ico|\\.mp4|\\.flv|\\.webp|/browser-sync/", 113 | // bgColor:"#2c2c2c", 114 | color:"#FF0000", 115 | // bold:"true", 116 | replaceWith:"http://xxxily.cc/", 117 | enabled:false 118 | } 119 | ], 120 | 121 | // 脚本注入 122 | scriptInject:[ 123 | { 124 | describe:"脚本注入使用示例", 125 | // 要注入的脚本路径,可以是本地目录下的脚本,也可以是线上URL脚本 126 | scriptPath:"D:\\work\\debugTools\\commonInject.js", 127 | // 指定脚本要放置在哪个dom标签里面,默认html 可选值有:html,body,head,title 128 | tagName:"head", 129 | // 指定放置在标签的哪个位置,默认是before 可选值有 before,after 130 | position:'after', 131 | /*禁止注入脚本的缓存,也就是为scriptPath增加时间戳参数,默认true*/ 132 | noCaching:true, 133 | /*条件限定*/ 134 | urlContain:[], 135 | urlUnContain:[], 136 | enabled: false 137 | } 138 | ], 139 | 140 | // 注意:如果匹配的链接过多,很容易导致:数组下标超限/未将对象应用设置到对象实例等错误弹窗提示 141 | callbackAcion:[ 142 | { 143 | describe: "回调操作示例代码", 144 | source:[ 145 | 'http://xxxily.cc/dispather-app/dispacher\\?method=dispacher' 146 | ], 147 | // exclude:[], 148 | include:[ 149 | '.html', 150 | '.jsp' 151 | ], 152 | // 可选值有:OnBeforeRequest OnPeekAtResponseHeaders OnBeforeResponse OnDone OnReturningError ,想匹配多个事件可以使用|进行分隔 153 | onEvent:'OnBeforeRequest', 154 | callback:function(oSession,eventName){ 155 | var t = this; 156 | console.log(eventName); 157 | 158 | if(eventName === 'OnBeforeRequest'){ 159 | var Cookie = oSession.oRequest['Cookie']; 160 | if(Cookie){ 161 | console.log(Cookie); 162 | }else { 163 | console.log('没找到对应的 Cookie'); 164 | } 165 | console.log('callbackTest:',oSession.fullUrl); 166 | oSession.oRequest['Cookie'] = "aaa"; 167 | } 168 | 169 | }, 170 | enabled: false 171 | }, 172 | { 173 | describe: "篡改登录信息示例", 174 | source:[ 175 | 'https://xxxily.cc/portal/userLoginAction!checkUser.action' 176 | ], 177 | onEvent:'OnBeforeRequest', 178 | callback:function(oSession,eventName){ 179 | var webForms = oSession.GetRequestBodyAsString(), 180 | strConv = coreApi.strConv, 181 | webFormsObj = strConv.parse(webForms); 182 | 183 | webFormsObj['username'] = "testUser"; 184 | webFormsObj['password'] = "testPw"; 185 | 186 | /*重设请求参数*/ 187 | oSession.utilSetRequestBody(strConv.stringify(webFormsObj)); 188 | }, 189 | enabled: false 190 | }, 191 | { 192 | describe: "本地脚本注入示例", 193 | source:[ 194 | "xxxily.net.cn", 195 | "xxxily.com.cn" 196 | ], 197 | include:[ 198 | '.html', 199 | '.jsp', 200 | 'vendor.js', 201 | 'commonInjectForDebug' 202 | ], 203 | onEvent:'OnBeforeResponse', 204 | callback:function(oSession,eventName){ 205 | 206 | /*给HTML页面注入调试脚本*/ 207 | if ( oSession.oResponse.headers.ExistsAndContains("Content-Type", "text/html") && oSession.utilFindInResponse("", false)>-1 ){ 208 | 209 | oSession.utilDecodeResponse(); 210 | var oBody = System.Text.Encoding.UTF8.GetString(oSession.responseBodyBytes); 211 | 212 | /*注入到head标签之前*/ 213 | var oRegEx = //i, 214 | scriptList = [ 215 | '', 216 | '\n' 217 | ]; 218 | oBody = oBody.replace(oRegEx, scriptList.join('')); 219 | 220 | oSession.utilSetResponseBody(oBody); 221 | } 222 | 223 | /*将注入的脚本地址内容替换成本地文件,实现本地脚本内容注入*/ 224 | if( oSession.fullUrl.indexOf('commonInjectForDebug') > -1 ){ 225 | oSession["x-replywithfile"] ="D:\\work\\debugTools\\commonInject.js"; 226 | } 227 | 228 | }, 229 | enabled: true 230 | } 231 | ], 232 | 233 | // 进行字符串查找,如果查找到将在Log面板显示查找结果 234 | Search: { 235 | inRequestHeaders: [], 236 | inResponseHeaders: [], 237 | inResponseBody: [] 238 | }, 239 | 240 | // 界面显示配置【可以对不同链接进行颜色标识,以便快速定位相关链接】 241 | UI:{ 242 | // 默认文本颜色 243 | color:"#c0c0c0",//灰白色 244 | // 默认背景颜色 245 | bgColor:"#2c2c2c",//浅黑 246 | bgColor_02:"#2f2f2f",//浅黑【用于做交替背景,可以不设置】 247 | // bgColor_02:"#4b4a4a", 248 | // 链接返回报错时的颜色 249 | onError:{ 250 | // bgColor:"#2c2c2c", 251 | color:"#FF0000" //错误红 252 | // ,bold:"true" 253 | }, 254 | // 不同关键词匹配对应的连接颜色,key 对应的是匹配的关键字,val对应的是匹配的颜色 255 | linkColor:{ 256 | "\\.jpg|\\.png|\\.gif":"#ffccff", //粉紫色 257 | "\\.js":"#00ff00", //原谅色 258 | "\\.css":"#ffcc66", //米黄 259 | "\\.html":"#00d7ff", //蓝色 260 | "\\.php":"#fff32d", //大黄 261 | "\\.jsp":"#fd4107" //砖红 262 | }, 263 | // 可以为特殊状态码设置不同颜色,方便快速定位一些错误链接,例如404等 264 | // 注意:这个只是根据responseCode 来匹配的,一些不存在response的链接配置是无效的,例如 502,504状态,应该是在onError里配置的 265 | statusCode:{ 266 | "404|408|500|502|504":"#FF0000", //错误红 267 | "304":"#5e5e5e" //浅灰色 268 | }, 269 | // 高亮,对特殊的链接进行高亮设置,方便跟踪查看链接 270 | highlight:{ 271 | "http://localhost|192.168":{ 272 | // bgColor:"#2c2c2c", //浅黑 273 | color:"#00ff00", //原谅色 274 | bold:"true", 275 | describe:"高亮测试" 276 | }, 277 | "hm.baidu.com":{ 278 | bgColor:"#FF0000", //红色 279 | color:"#fdf404", //黄色 280 | bold:"true", 281 | describe:"高亮测试" 282 | }, 283 | "":"" 284 | } 285 | }, 286 | // 一些实用工具集,先列个可能会开发的工具集,留个坑以后有时间再开发 287 | Tools:{ 288 | // TODO API 测试工具 289 | apiTest:{}, 290 | // TODO 重放攻击工具 291 | replay:{}, 292 | // TODO 内容注入工具 293 | contentInject:{}, 294 | // TODO 类似 weinre 这样的注入调试工具 295 | weinre:{} 296 | }, 297 | // 多项分隔符号【同一个配置需匹配多项规则时可以通过分隔符进行区分,这样就不用每个规则都要新开一份配置那么繁琐】 298 | splitStr:"|", 299 | // 正则匹配的修饰符:i,g,m 默认i,不区分大小写 300 | regAttr:"i" 301 | }; 302 | //全局配置项 END 303 | ``` 304 | 305 | 如果需要进行要禁用某些规则,只需在菜单栏里面操作选择开启或禁用即可:Rules > Fiddler-plus 306 | 307 | 308 | 特别说明:Fiddler 的 CustomRules.js 修改配置保存后是会自动立即生效的,无需重启 309 | 所以做服务器代理转发、切换开发环境的时候,写好配置后,只需打开或注释掉某行配置,然后保存即可实现实时切换 310 | 311 | 312 | 目前主要实现了:代理、替换、过滤、UI(skin)等功能;已经可满足绝大部分开发需求了,后续将继续完善 313 | 314 | 暂时先这样,后续等代码完善好了再补充说明文档... 315 | 316 | ## 开发计划: 317 | 1、完善替换功能,实现替换本地文件 318 | 2、完善搜索查找功能 319 | 2、实现搜索替换和注入等功能 320 | ~~1、UI(skin)后续打算实现成多套可选的形式,然后可以针对域名指定不同的配色方案,这样就不用隐藏连接也可以快速区分哪些是当前需要关注的连接。~~ 321 |
322 | ~~2、全局禁止缓存感觉很蠢,严重影响正常上网体验,所以缓存也计划加入到 replacePlus 配置项里,针对性禁止缓存~~ 323 | 324 | 325 | -------------------------------------------------------------------------------- /assets/screenshot/screenshot01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxxily/Fiddler-plus/e5e430e0c27acf65e26fa37ee3b067201b78870e/assets/screenshot/screenshot01.png -------------------------------------------------------------------------------- /assets/screenshot/screenshot02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxxily/Fiddler-plus/e5e430e0c27acf65e26fa37ee3b067201b78870e/assets/screenshot/screenshot02.png -------------------------------------------------------------------------------- /doc/FiddlerScript_api_reference.md: -------------------------------------------------------------------------------- 1 | # FiddlerScript api 参考文档 2 | 3 | 主要是修改CustomRules时个人摘抄自 Fiddler scriptEditor 里面的API说明 4 | 有些翻译不一定准确,请自行辨别 5 | 6 | 7 | ## Seesion对象 8 | * m_clientIP:System.String 当前连接使用客户机的IP 9 | * m_clientPort:System.Int32 当前连接使用客户机的端口 10 | * oFlags:System.Collections.Specialized.StringDictionary 11 | * oRequest:Fiddler.ClientChatter HTTP的Request对象 12 | * oResponse:Fiddler.ServerChatter HTTP的Response对象 13 | * requestBodyBytes:System.Byte[] request body 包含的字节内容 14 | * responseBodyBytes:System.Byte[] response body 包含的字节内容 15 | * ViewItem:System.Windows.Forms.ListViewItem ListViewItem object associated with this session in the Session list 16 | * bHasResponse:System.Boolean 如果存在response时则返回true 17 | * bHasWebSocketMessages:System.Boolean 18 | * BitFlags:Fiddler.SessionFlags 19 | * bypassGateway:System.Boolean 如果在OnBeforeRequest中将该参数设置为true,则请求将绕过网关 20 | * clientIP:System.String 返回客户端通过Fidder的地址 21 | * clientPort:System.Int32 返回客户端通过Fidder的端口 22 | * fullUrl:System.String 返回完整的URL地址 23 | * host:System.String 设置或返回请求的host,包括端口 24 | * hostname:System.String 设置或返回请求的host名称,不包括端口 25 | * id:System.Int32 返回请求的序号 26 | * isFTP:System.Boolean 返回使用的是否为ftp协议 27 | * isHTTPS:System.Boolean 返回使用的是否为https协议 28 | * isTunnel:System.Boolean 返回使用的是否为tunnel隧道协议 29 | * Item:System.String Indexer property into session flags, collection oSession["Flagname"] return string value (or null if missing) 30 | * Item:System.String Indexer property into SESSION flags,REQUEST headers.and RESPONSE headers e.g. oSession["Requst","Host"] 31 | * LocalProcess:System.String 32 | * LocalProcessID:System.Int32 33 | * PathAndQuery:System.String 返回url的路径以及查询部分字符串 34 | * port:System.Int32 返回请求的主机端口 35 | * RequestBody:System.Byte[] 以字节的形式设置或获取请求的内容 36 | * RequestHeaders:Fiddler.HTTPRequestHeaders 获取请求的头部信息,如果不存在则返回空 37 | * RequestMethod:System.String 设置或返回请求的方法,例如:get、post等 38 | * ResponseBody:System.Byte[] 以字节的形式设置或获取response的内容 39 | * responseCode:System.Int32 设置或获取response的状态码 40 | * ResponseHeaders:Fiddler.HTTPResponseHeaders 获取回应的头部信息,如果不存在则返回空 eg:oSession.ResponseHeaders.Item("Content-Type") 41 | * state:Fiddler.SessionStates 枚举当前session的状态 42 | * SuggestedFilename:System.String 建议使用的进行文件保存时的名称 43 | * TunnelEgressByteCount:System.Int64 44 | * TunnelIngressByteCount:System.Int64 45 | * TunnelIsOpen:System.Boolean 46 | * url:System.String 在请求之前获取或设置URL(不包括端口) 47 | * actInspectInNewWindow(sActiveTab ) 对当前session新开一个审计窗口,指定tab的title 48 | * GetRequestBodyAsString() 获取request body 的字符串 49 | * GetRequestBodyEncoding() 获取request body 编码形式 50 | * GetResponseBodyAsString() 获取response body 的字符串 51 | * GetResponseBodyEncoding() 获取response body 编码形式 52 | * GetResponseBodyHash(sHashAlg ) 获取ResponseBody内容的一个md5/sha1/sha256/sha384/sha512的hash值 53 | * GetResponseBodyHashAsBase64(sHashAlgorithm ) 54 | * HostnameIs(sTestHost ) 判断是否存在某个host,不包含端口 55 | * HTTPMethodIs(sTestFor ) 判断HTTP请求使用的方法 56 | * Ignore() 忽略session 57 | * LoadRequestBodyFromFile(sFilename) 使用指定的文件替换掉当前session请求的headers和body内容 58 | * LoadResponseFromFile(sFilename) 使用指定的文件替换掉当前session返回的headers和body内容 59 | * RefreshUI() 更新当前session的显示界面 60 | * SaveRequestBody(sFilename ) 保存request body到本地 61 | * SaveResponseBody() 保存HTTP response body 到Fiddler 的捉包文件夹 62 | * SaveResponseBody(sFilename ) 保存HTTP response body 到本地 63 | * uriContains(sLookfor ) 当uri包含指定字符串时返回true 64 | * utilAssignResponse(oFromSession ) 绕开未就绪的连接请求,复制一个已存在的session response 到当前session 65 | * utilAssignResponse(oRH arrBody ) 66 | * utilBZIP2Response() 使用BZIP2 对 response body进行压缩. 抛出异常到caller 67 | * utilChunkResponse(iSuggestedChunkCount ) 68 | * utilCreateResponseAndBypassServer() call inside OnBeforeRequest to create a Response object an bypass the server 69 | * utilDecodeRequest() 移除当前请求的chunking 和 HTTP Compression ,添加或更新hader的Content-Length字段 70 | * utilDecodeResponse() 71 | * utilDeflateResponse() 使用DEFLATE 压缩response. 抛出异常到caller 72 | * utilFindInRequest(sSearchFor bCaseSensitive ) 在request body进行字符串搜索,返回当前索引或-1 73 | * utilFindInResponse(sSearchFor bCaseSensitive ) 74 | * utilGZIPRequest() 使用GZIP压缩请求内容。抛出异常到caller 75 | * utilGZIPResponse() 使用GZIP压缩response。抛出异常到caller 76 | * utilPrependToResponseBody(sString ) 将字符串插入到response body 前,更新header的Content-Length字段.注意:utilDecodeResponse 77 | * utilReplaceInRequest(sSearchFor sReplaceWith ) 替换请求的内容【区分大小写】(不是url内容),并自动更新header的Content-Length字段,如果替换成功则返回true 78 | * utilReplaceInResponse(sSearchFor sReplaceWith ) 替换返回的内容【区分大小写】,并自动更新header的Content-Length字段,如果替换成功则返回true,注意:使用该方法前应先调用utilDecodeResponse 79 | * utilReplaceOnceInResponse(sSearchFor sReplaceWith bCaseSensitive ) 80 | * utilReplaceRegexInResponse(sSearchForRegEx sReplaceWithExpression ) 81 | * utilSetRequestBody(sString ) 用字符串替换掉原来的请求内容,并自动更新header的Content-Length字段,移除Transfer-Encoding/Content-Encoding 82 | * utilSetResponseBody(sString ) 用字符串替换掉原来的返回内容,并自动更新header的Content-Length字段,移除Transfer-Encoding/Content-Encoding 83 | * WriteToStream(oFS bHeadersOnly ) 将session或session的header写入指定流里 84 | * FiddlerObject.log( "当前测试字段:"+oSession.GetResponseBodyEncoding() ); 85 | 86 | 87 | 88 | ## oSession.oRequest.headers 对象 89 | * HTTPMethod:System.String 90 | * HTTPVersion:System.String 91 | * Item:Fiddler.HTTPHeaderItem 92 | * Item:System.String 93 | * RawPath:System.Byte[] 94 | * RequestPath:System.String 95 | * UriScheme:System.String 96 | * UriUserInfo:System.String 97 | * Add(sHeaderName sValue ) 98 | * AssignFromString(sHeaders ) 99 | * Count() 100 | * Exists(sHeaderName ) 101 | * ExistsAndContains(sHeaderName sHeaderValue ) 102 | * ExistsAndEquals(sHeaderName sHeaderValue ) 103 | * ExistsAny(sHeaderNames ) 104 | * GetTokenValue(sHeaderName sTokenName ) 105 | * Remove(sHeaderName ) 106 | * RemoveRange(arrToRemove ) 107 | * RenameHeaderItems(sOldHeaderName sNewHeaderName ) 108 | * ToByteArray(prependVerbLine appendEmptyLine includeProtocolInPath ) 109 | * ToByteArray(prependVerbLine appendEmptyLine includeProtocolInPath sVerbLineHost ) 110 | * ToString() 111 | * ToString(prependVerbLine appendEmptyLine ) 112 | * ToString(prependVerbLine appendEmptyLine includeProtocolAndHostInPath ) 113 | 114 | ## oSession.oResponse.headers 对象 115 | * HTTPResponseCode:System.Int32 116 | * HTTPResponseStatus:System.String 117 | * HTTPVersion:System.String 118 | * Item:Fiddler.HTTPHeaderItem 119 | * Item:System.String 120 | * StatusDescription:System.String 121 | * Add(sHeaderName sValue ) 122 | * AssignFromString(sHeaders ) 123 | * Count() 124 | * Exists(sHeaderName ) 125 | * ExistsAndContains(sHeaderName sHeaderValue ) 126 | * ExistsAndEquals(sHeaderName sHeaderValue ) 127 | * ExistsAny(sHeaderNames ) 128 | * GetTokenValue(sHeaderName sTokenName ) 129 | * Remove(sHeaderName ) 130 | * RemoveRange(arrToRemove ) 131 | * RenameHeaderItems(sOldHeaderName sNewHeaderName ) 132 | * ToByteArray(prependStatusLine appendEmptyLine ) 133 | * ToString() 134 | * ToString(prependStatusLine appendEmptyLine ) 135 | 136 | 137 | ## FiddlerObject 对象 138 | * FileExtension:System.String 139 | * Language:Fiddler.ScriptLanguage 140 | * StatusText:System.String 141 | * UI:Fiddler.frmViewer 142 | * alert(sMessage ) 143 | * createDictionary() 144 | * flashWindow() 145 | * log(sMessage ) 146 | * playSound(sSoundname ) 147 | * prompt(sMessage ) 148 | * prompt(sMessage sDefaultValue ) 149 | * prompt(sMessage sDefaultValue sWindowTitle ) 150 | * ReloadScript() 151 | * uiInvoke(oMI ) 152 | * uiInvokeAsync(oMI args ) 153 | * utilIssueRequest(sRequest ) 154 | * WatchPreference(sPref oFN ) 155 | 156 | -------------------------------------------------------------------------------- /doc/Fiddler教程.md: -------------------------------------------------------------------------------- 1 | # 目录 2 | * [Fiddler介绍](#Fiddler介绍) 3 | * [Fiddler是什么](#Fiddler是什么) 4 | * [Fiddler能做什么](#Fiddler能做什么) 5 | * [Fiddler的工作原理](#Fiddler的工作原理) 6 | * [为什么选择Fiddler](#为什么选择Fiddler) 7 | * [Fiddler初级教程](#更多教程内容还在编写中...) 8 | * [完全的新手教程、主要讲解下载、安装、界面、选项等](#更多教程内容还在编写中...) 9 | * [Fiddler中级教程](#Fiddler中级教程) 10 | * [主要讲解通过Fiddler提供的各种界面选项,命令选项等进行抓包调试、技巧等](#更多教程内容还在编写中...) 11 | * [Fiddler高级教程](#更多教程内容还在编写中...) 12 | * [FiddlerScript 系统讲解,进行个性化定制、Fiddler plus 使用教程等](#更多教程内容还在编写中...) 13 | 14 | ## Fiddler是什么 15 | Fiddler是位于客户端和服务器端的HTTP(s)代理,也是目前最常用的http(s)抓包工具之一 。 16 | 它能够记录客户端和服务器之间的所有 HTTP请求,可以针对特定的HTTP请求,分析请求数据、设置断点、调试web应用、修改请求的数据,甚至可以修改服务器返回的数据,功能非常强大,是web调试的利器。 17 | 18 | ## Fiddler能做什么 19 | 20 | * 进行任意平台(PC端、移动端等)、任意应用的抓包分析与开发调试 21 | * 解决跨域开发时的接口、资源访问限制问题 22 | * 进行多环境快速切换调试 23 | * 快速调试线上脚本,排除故障 24 | * 进行弱网络环境模拟测试,暴露应用在弱网下的用户体验 25 | * 进行接口性能测试 26 | * 自动化接口测试 27 | * 模拟各种网络攻击 28 | * 还有各种不可描述的功能... 29 | 30 | ## Fiddler的工作原理 31 | Fiddler的本质是一个代理,既然是代理,也就是说:客户端的所有请求都要先经过Fiddler,然后转发到相应的服务器,反之,服务器端的所有响应,也都会先经过Fiddler然后发送到客户端。 32 | 当我们启用Fiddler之后,IE的http(s)代理会自动被设置为127.0.0.1:8888(之所以这么设置是因为Fiddler默认监听的是8888端口), 所有http(s)会话的都会被Fiddler拦截,web客户端和服务器的请求如下所示: 33 | ![Fiddler](img/pic001.png) 34 | 35 | 至于Fiddler为啥可以抓https包,其实是利用了类似`中间人攻击`的技术,所以开启https抓包的时候需要安装个证书。 36 | 37 | ## 为什么选择Fiddler 38 | 说到抓包工具,稍微了解的都知道有很多类似的工具可用,例如:Wireshark、BurpSuite、Fiddler、Charles、HTTP Analyzer、HTTP Watch等,新兴的基于nodejs的还有:anyproxy、whistle等 39 | 那么多抓包工具,为什么偏偏要选择Fiddler呢? 40 | 原则上青菜萝卜各有所爱,每个人选择适合自己,能满足自己需求的即可。 41 | 但对于不熟悉各个工具特点的人,该如何选择适合自己的抓包软件,却是十分头痛的一件事,所以下面简单谈谈这些不同抓包工具的特点和适用范围: 42 | * [Wireshark](https://www.wireshark.org/) 是一款免费开源的网络封包分析软件,属于比较底层的抓包软件,需要比较专业的人员才能驾驭,主要面向网络管理员,网络安全工程师,普通人一般用它来学习网络协议等知识。 43 | * [BurpSuite](https://portswigger.net/burp/) 虽然也可以抓包分析,但它是用于攻击web应用程序的集成平台,包含了很多工具集,可以对web应用进行安全评估,漏洞挖掘等。 如果你是个网络安全人员,或打算往网络安全方面发展,可以学习使用。ps:这是收费产品 44 | * [HTTP Analyzer](http://www.ieinspector.com/)、[HTTP Watch](http://www.httpwatch.com/)都是是比较老牌的HTTP抓包工具了,且都属收费产品,不能进行个性化定制或个性化定制功能较弱,从搜索结果和文章更新度来看已经比较少人使用了,所以个人也不推荐使用。 45 | * [Charles](https://www.charlesproxy.com/) 界面简洁,功能强大,且同时支持Mac平台和Win平台,是不错的HTTP(s)捉包分析软件,但属于收费产品,个性化定制方面也比较弱,不喜欢折腾、且有点小钱的可以考虑。ps:什么?你有破解版? 46 | * [Fiddler](http://www.telerik.com/fiddler) 功能强大,HTTP(s)类抓包软件应有的功能都有,而且是免费,但界面一般,配置界面繁杂(这也是功能强大的一个体现吧...)~好在拥有FiddlerScript,支持个性化定制,插件开发等,你完全可以通过它打造出一个属于自己的调试工具,当然前提是你要有一定的折腾精神。本文后面的高级教程将教你如何定制自己的Fiddler,打造一个高逼格且高效的Fiddler 47 | * [anyproxy](https://github.com/alibaba/anyproxy)、[whistle](https://github.com/avwo/whistle) 是基于nodejs开发,具有良好的跨平台特性,而且源码开放,有很强的可定制性!非常适合喜欢折腾的极客“把玩”!当然目前也有不少缺点,例如进行https抓包时卡顿,存在一定的不稳定情况,anyproxy已经很久没人更新维护等。总的来说类免费、开源、跨平台的产品可以还需继续努力完善!希望有一天可以完全取代Fiddler吧! 48 | 49 | ## Fiddler初级教程 50 | 51 | 该部分纯粹为新手教程,主要讲解Fiddler的下载安装、界面、选项等,如果你不是新手,请跳过本章内容。 52 | ## 下载 53 | Fiddler的官网地址为:http://www.telerik.com/fiddler 54 | 下载地址:https://www.telerik.com/download/fiddler 55 | 进入下载页面后你将看到如下界面: 56 | 57 | ![Fiddler](img/downloadFiddler_dec01.png) 58 | 59 | 可以看到Fiddler要求你选择一个使用它的用途,随便选择一个选项即可, 60 | 下面还有两个下载链接,一个是下载OS X(Mac)版本的Fiddler,另外一个是Linux版本的Fiddler,如果你有需要,可以点击下载安装,具体安装步骤和过程这里不介绍~ 61 | 62 | 选择用途后出现如下界面: 63 | 64 | ![Fiddler](img/downloadFiddler_dec02.png) 65 | 66 | 出现了是否同意使用的用户协议选项,选择Yes即可,然后又出现如下的填写用户邮箱界面: 67 | 68 | ![Fiddler](img/downloadFiddler_dec03.png) 69 | 70 | 随便填写一个格式正确的邮箱地址即可点击下载按钮Z进行下载了,跳到下载页面,等待一段时间就会开始下载。 71 | 下载好后双击即可开始安装了。。。 72 | 73 | 如果你觉得以上步骤打开太慢,操作繁琐,可以到百度软件中心下载,详细地址请自行百度搜索:Fiddler 。 74 | 75 | ## 安装 76 | 77 | ## 基本界面和功能介绍 78 | 打开Fiddler,Fiddler主界面的布局如下: 79 | 80 | ![Fiddler](img/UI_base.png) 81 | 82 | 主界面中包括六打常用的模块,其中: 83 | 84 | 1、Fiddler的菜单栏包含了大量的功能操作项和设置项 85 | 2、Fiddler的工具栏,也就是菜单栏下面的一栏,包含了常用的快捷操作按钮,如清除会话信息、开启/禁止解码功能、设置sessions面板(URL面板)最大显示条数、保存会话信息、重放操作等。 86 | 87 | 3、web Session面板,主要是Fiddler抓取到的每一条http请求(每一条称为一个session),主要包含了请求的url,协议,状态码,body等信息,详细的字段含义如下: 88 | 89 | 1.[#] -- HTTP 请求的链接类型图标和请求的顺序
90 | 2.[Result] -- HTTP 响应的状态码,相关状态码信息可以参考 [这里](https://segmentfault.com/a/1190000012282437) 和 [这里](http://tool.oschina.net/commons?type=5)
91 | 3.[Protocol] -- 请求使用的协议(如 HTTP/HTTPS/WS )
92 | 4.[Host] -- 请求地址的域名
93 | 5.[URL] -- 请求的服务器路径和文件名,也包括GET参数
94 | 6.[Body] -- 请求的大小,以byte为单位
95 | 7.[Caching] -- 请求的缓存过期时间或缓存控制的 header 值
96 | 8.[Content-Type] -- 请求响应的类型([Content-Type](http://tool.oschina.net/commons))
97 | 9.[Process] -- 发出此请求的 window 进程名称或进程ID
98 | 10.[Comments] -- 用户给此 session 增加的备注信息
99 | 11.[Custom] -- 用户可以通过脚本设置的自定义值
100 | 101 | 102 | 103 | 104 | ## 更多教程内容还在编写中... 105 | 太懒了,什么时候有冲动了再继续编写。。 106 | ~~更多教程内容还在编写中,如果你急不可耐,可以阅看下面的参考资料,教程将根据这些参考资料以及个人的经验进行编写~~ 107 | 108 | ## 参考资料 109 | https://www.qcloud.com/community/article/115124?fromSource=gwzcw.93596.93596.93596 110 | http://blog.csdn.net/ohmygirl/article/details/17846199 111 | [Fiddler调试权威指南 Debugging with Fiddle](https://item.jd.com/11398605.html) -------------------------------------------------------------------------------- /doc/img/UI_base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxxily/Fiddler-plus/e5e430e0c27acf65e26fa37ee3b067201b78870e/doc/img/UI_base.png -------------------------------------------------------------------------------- /doc/img/downloadFiddler01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxxily/Fiddler-plus/e5e430e0c27acf65e26fa37ee3b067201b78870e/doc/img/downloadFiddler01.png -------------------------------------------------------------------------------- /doc/img/downloadFiddler_dec01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxxily/Fiddler-plus/e5e430e0c27acf65e26fa37ee3b067201b78870e/doc/img/downloadFiddler_dec01.png -------------------------------------------------------------------------------- /doc/img/downloadFiddler_dec02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxxily/Fiddler-plus/e5e430e0c27acf65e26fa37ee3b067201b78870e/doc/img/downloadFiddler_dec02.png -------------------------------------------------------------------------------- /doc/img/downloadFiddler_dec03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxxily/Fiddler-plus/e5e430e0c27acf65e26fa37ee3b067201b78870e/doc/img/downloadFiddler_dec03.png -------------------------------------------------------------------------------- /doc/img/pic001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xxxily/Fiddler-plus/e5e430e0c27acf65e26fa37ee3b067201b78870e/doc/img/pic001.png --------------------------------------------------------------------------------