上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
1234下一页
返回列表 发新帖

[油猴脚本开发指南]理解fetch劫持

[复制链接]
  • TA的每日心情
    开心
    3 小时前
  • 签到天数: 56 天

    [LV.5]常住居民I

    352

    主题

    3110

    帖子

    3115

    积分

    管理员

    非物质文化遗产社会摇传承人

    Rank: 10Rank: 10Rank: 10

    积分
    3115

    猫咪币纪念章国庆纪念章中秋纪念章荣誉开发者家财万贯管理员

    发表于 2021-8-25 18:43:11 | 显示全部楼层 | 阅读模式

    前言

    Fetch劫持是基于Promise进行的,我们之前已经学习过了Promise,那么现在我们来研究一下fetch劫持

    2021-8-28留

    以下代码可能存在错误,如果想直接摘抄请使用这个代码

    2021-8-28下午留

    我又参考了一部分代码和资料,提出一个相对来说目前可能正确的fetch代理

    之前代码出现报错是因为this指向问题,为了防止调用函数出错,我们传入了原response的this指针,然后有些网页喜欢往上挂载函数以及乱读乱写

    所以获取了this指针后就饶过了我们的proxy代理,并且还在使用proxy代理以及非proxy指针,导致冲突问题。

    这里我们使用了proxy直接传入原reponse以及代理原reponse对象

    保证写入的数据都在reponse上,以及网页获取的this也在reponse上

    这时候调用函数会经过我们的proxy上,可以比之前的可靠性更好一点

    如果使用该代码出现错误请立刻停止继续使用,并反馈。

    更推荐使用cxxjackie的进行fetch劫持!!!

    let oldfetch=fetch
    function newobj(){}
    function fuckfetch(...bianliang){
        return new Promise(function(resolve, reject){
            oldfetch(...bianliang).then(function(response) {
                let handler = {
                    get: function(target, prop, receiver) {
                        console.log('get',target, prop, receiver)
                        if(typeof Reflect.get(target,prop)==='function')
                        {
                            if(Reflect.get(target,prop+'proxy')===undefined)
                            {
                                target[prop+'proxy']=new Proxy(Reflect.get(target,prop), {
                                    apply: (target, thisArg, argumentsList)=> {
                                        console.log('fetchfunction',target.name, response, argumentsList)
                                        return Reflect.apply(target, response, argumentsList);
                                    }
                                });
                            }
                            return Reflect.get(target,prop+'proxy')
                        }
    
                        return Reflect.get(target, prop);
                    },
    
                    set(target, prop, value) {
                        return Reflect.set(target, prop, value);
                    },
                };
                let proxy=new Proxy(response, handler)
                resolve(proxy)
            })
    
        });
    }
    
    window.fetch=fuckfetch

    开始

    一个基本的fetch还是很简单的

    fetch('http://example.com/movies.json')
      .then(function(response) {
        return response.json();
      })
      .then(function(myJson) {
        console.log(myJson);
      });

    fetch提交一个地址,然后返回了一个promise,对promise挂载hen,收到了一个response,然后返回response的json函数返回的内容

    图片.png

    我们可以看到现在a是履行的

    返回的是一个Response对象,并且json在原型链上

    图片.png

    为了保守性和通用性,我们应该对promise返回的response对象进行劫持

    这里我们开始对fetch函数进行劫持吧!

    实战

    let oldfetch=fetch
    function fuckfetch(arguments){
       return new Promise(function(resolve, reject){
    
            });
      }
    

    由于fetch返回了一个promise,所以我们也需要返回一个promise,oldfetch是为了保存原fetch的引用,因为我们会替换掉window上fetch函数,对fetch实现一个劫持的功能。arguments是为了收集传入函数的参数,并且原封不动的传入到原fetch中。

    let oldfetch=fetch
    function fuckfetch(arguments){
       return new Promise(function(resolve, reject){
            oldfetch(arguments)
            });
      }

    由于我们是劫持fetch函数嘛,我们需要在promise进行fetch的函数调用,并传入调用我们函数的参数原封不动的传给他,只要我们不resolve/reject,这个函数永远都是pengding的状态,所以我们可以放心干,大胆干!

    let oldfetch=fetch
    function fuckfetch(arguments){
       return new Promise(function(resolve, reject){
            oldfetch(arguments).then(function(response) {
                        console.log(response)
                 resolve('niub')
                      })
    
            });
      }

    这里对原fetch返回的promise挂载了一个then,收到response并输出出来,然后将我们这个函数的返回的promise设置为履行,值为国粹niub。

    我们首先测试一下

    图片.png

    可以看到输出的url并不是我们提交的,也报错了一个404

    我们调试一下看看,因为我们的函数没有实际功能,所以100%是在oldfetch上出错了,打断点看一点

    图片.png

    这里看到arguments是new promise传给我们的参数,我们疏忽了!

    我们应该获取到fuckfetch上的参数,应该怎么办呢?

    可以考虑用变量拓展运算符

    let oldfetch=fetch
    function fuckfetch(...bianliang){
       return new Promise(function(resolve, reject){
            oldfetch(...bianliang).then(function(response) {
                        console.log(response)
                 resolve('Fuccck')
                      })
    
            });
      }
    window.fetch=fuckfetch
    

    ...会收集没有赋值到参数上变量的所有其他剩余参数,而在函数体内对变量使用...,则会将其还原至原来的参数样子。

    因为我们这里没有其他参数,所以...bianliang会收集所有传给这个函数的参数。

    图片.png

    可以看到...c收集了所有剩余参数。

    图片.png

    而在函数体内使用...c,会将收集的剩余参数形成的数组重新还原成参数的形式

    图片.png

    修改之后可以看到非常成功,然后我们可以开始对Response对象进行Proxy劫持了!

    let oldfetch=fetch
    function fuckfetch(...bianliang){
        return new Promise(function(resolve, reject){
            oldfetch(...bianliang).then(function(response) {
    
                let handler = {
                    get: function(target, prop, receiver) {
                        console.log('target',target, prop, receiver)
                        return Reflect.get(target, prop);
                    },
    
                    set(target, prop, value) {
                        console.log('set',target, prop, value)
                        return Reflect.set(target, prop, value);
                    },
                };
                let proxy=new Proxy(response, handler)
                resolve(proxy)
            })
    
        });
    }
    
    window.fetch=fuckfetch

    由于对对象常见的操作我认为只有set和get,所以这里目前仅对这两个操作进行劫持,如果存在其他的,我们可以查询手册,继续添加。

    对其进行调用测试,发现报出错误

    图片.png

    a.then(function(response) {console.log('getresponse',response)
    return response.json();
    })

    我们使用的是response.json()

    会触发get属性,获取到json的函数并且调用,但是由于我们进行了target包装

    所以调用的变成了Proxy对象的json函数,由于根本没有,所以会触发报错。

    这里会不会想到xhr劫持部分?我们可以模仿xhr劫持部分对函数的指向进行劫持。

    let oldfetch=fetch
    function newobj(){}
    function fuckfetch(...bianliang){
        return new Promise(function(resolve, reject){
            oldfetch(...bianliang).then(function(response) {
                let tagetobk=new newobj();
                tagetobk.oldfetch=response;
    
                let handler = {
                    get: function(target, prop, receiver) {
                        console.log('get',target, prop, receiver)
                                    if(prop==='oldfetch'){
                    return Reflect.get(target,prop);
                }
                        if(typeof Reflect.get(target.oldfetch,prop)==='function')
                        {
                            if(Reflect.get(target.oldfetch,prop+'proxy')===undefined)
                            {
                                target.oldfetch[prop+'proxy']=new Proxy(Reflect.get(target.oldfetch,prop), {
                                    apply: function(target, thisArg, argumentsList) {
                                        return Reflect.apply(target, thisArg.oldfetch, argumentsList);
                                    }
                                });
    
                            }
                            return Reflect.get(target.oldfetch,prop+'proxy')
                        }
                        return Reflect.get(target.oldfetch, prop);
                    },
    
                    set(target, prop, value) {
                        console.log('set',target, prop, value)
                        return Reflect.set(target.oldfetch, prop, value);
                    },
                };
                let proxy=new Proxy(tagetobk, handler)
                resolve(proxy)
            })
    
        });
    }
    
    window.fetch=fuckfetch

    我们可以在函数的proxy内进行判断

                                target.oldfetch[prop+'proxy']=new Proxy(Reflect.get(target.oldfetch,prop), {
                                    apply: function(target, thisArg, argumentsList) {
                                        console.log('function',target, thisArg, argumentsList)
                                        if(target.name==='json'){
                                            return {a:6,b:6,c:6}
                                        }
                                        return Reflect.apply(target, thisArg.oldfetch, argumentsList);
                                    }
                                });

    这里我输出了function+target,thisArg,argumentsList三个参数

    第一个target是我们调用的目标函数,我们可以对这个函数调用.name来获得他的函数名字符,然后与json对比,如果相等了

    就返回一些我们特定的内容,如果不相等,则调用原来的函数并返回内容。

    图片.png

    图片.png

    图片.png

    这里可以看到返回了我们设定的内容,这个对象下的其他函数也可以通过这种办法进行劫持,这里只是以json函数进行举例

    警告

    proxy会拖慢网页的执行效率,在频繁数据交互的情况下不推荐使用,以及希望大家时候的时候可以考虑在fuckfetch函数内对参数进行判断,有针对性的对fetch进行劫持过滤,proxy虽好,可不要贪杯哦~

    结语

    撒花~

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    13

    主题

    416

    帖子

    723

    积分

    荣誉开发者

    Rank: 10Rank: 10Rank: 10

    积分
    723

    活跃会员热心会员突出贡献三好学生猫咪币纪念章中秋纪念章国庆纪念章荣誉开发者

    发表于 2021-8-25 21:22:35 | 显示全部楼层

    我还是不喜欢Proxy这种劫持整个对象的做法,效率问题先放一边,看你的代码逻辑也显得非常复杂。像这个情景下只关心response.json(),那就劫持json好了,这个函数也是Promise,那就像劫持fetch一样再套一层Promise:

    let oldfetch = fetch;
    function fuckfetch() {
        return new Promise((resolve, reject) => {
            oldfetch.apply(this, arguments).then(response => {
                const oldJson = response.json;
                response.json = function() {
                    return new Promise((resolve, reject) => {
                        oldJson.apply(this, arguments).then(result => {
                            result.hook = 'success';
                            resolve(result);
                        });
                    });
                };
                resolve(response);
            });
        });
    }
    window.fetch = fuckfetch;

    这里利用了箭头函数的一个特性:不改变this和arguments的指向。由于Promise用了箭头函数,oldfetch.apply(this, arguments)这里的this和arguments其实指的是fuckfetch的this和arguments,这样就可以省去传参这一步,我下面劫持response.json又用回function,所以oldJson.apply(this, arguments)这句的this和arguments指向的是response.json(我是故意这么写的,其实这里的this等于response,arguments是空,所以代码可以写成oldJson.call(response))。箭头函数不只是写着好看的,很多时候多利用这些特性,可以解决很多问题。

    回复

    使用道具 举报

  • TA的每日心情
    开心
    3 小时前
  • 签到天数: 56 天

    [LV.5]常住居民I

    352

    主题

    3110

    帖子

    3115

    积分

    管理员

    非物质文化遗产社会摇传承人

    Rank: 10Rank: 10Rank: 10

    积分
    3115

    猫咪币纪念章国庆纪念章中秋纪念章荣誉开发者家财万贯管理员

    发表于 2021-8-25 22:13:00 | 显示全部楼层

    cxxjackie 发表于 2021-8-25 21:22

    [md]我还是不喜欢Proxy这种劫持整个对象的做法,效率问题先放一边,看你的代码逻辑也显得非常复杂。像这个 ...

    我还是不喜欢Proxy这种劫持整个对象的做法,效率问题先放一边,看你的代码逻辑也显得非常复杂。像这个 ...[/quote]
    也想过用这种方法,但是想了想感觉搞个通用的例子更普适一点
    代码确实太复杂了,写这鬼东西我调了来来回回快半个小时....
    下节课以哥哥的为例子我拆了说一下{:4_110:}
    哥哥的挺nice!

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    3 小时前
  • 签到天数: 167 天

    [LV.7]常住居民III

    25

    主题

    647

    帖子

    6273

    积分

    荣誉开发者

    精通各种语言的HelloWord!

    Rank: 10Rank: 10Rank: 10

    积分
    6273

    猫咪币纪念章活跃会员三好学生热心会员中秋纪念章国庆纪念章荣誉开发者家财万贯

    发表于 2021-8-25 23:17:50 | 显示全部楼层
    ggnb!!!!!!!!
    回复

    使用道具 举报

  • TA的每日心情
    开心
    3 小时前
  • 签到天数: 56 天

    [LV.5]常住居民I

    352

    主题

    3110

    帖子

    3115

    积分

    管理员

    非物质文化遗产社会摇传承人

    Rank: 10Rank: 10Rank: 10

    积分
    3115

    猫咪币纪念章国庆纪念章中秋纪念章荣誉开发者家财万贯管理员

    发表于 2021-8-26 09:08:25 | 显示全部楼层

    哥哥牛逼!
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    擦汗
    2022-6-12 21:47
  • 签到天数: 21 天

    [LV.4]偶尔看看III

    11

    主题

    256

    帖子

    203

    积分

    高级工程师

    Rank: 6Rank: 6

    积分
    203

    中秋纪念章猫咪币纪念章活跃会员热心会员三好学生

    发表于 2021-8-27 19:56:19 | 显示全部楼层
    呜,二楼的哥哥代码似乎更简洁一点,但我想李大道大神是以教学为目的,出发点不一样吧,虽然我没看懂。试了一下二楼代码是可以直接拿来用的呀,哎呀我靠
    回复

    使用道具 举报

  • TA的每日心情
    擦汗
    2022-6-12 21:47
  • 签到天数: 21 天

    [LV.4]偶尔看看III

    11

    主题

    256

    帖子

    203

    积分

    高级工程师

    Rank: 6Rank: 6

    积分
    203

    中秋纪念章猫咪币纪念章活跃会员热心会员三好学生

    发表于 2021-8-27 20:17:29 | 显示全部楼层

    cxxjackie 发表于 2021-8-25 21:22

    [md]我还是不喜欢Proxy这种劫持整个对象的做法,效率问题先放一边,看你的代码逻辑也显得非常复杂。像这个 ...

    我还是不喜欢Proxy这种劫持整个对象的做法,效率问题先放一边,看你的代码逻辑也显得非常复杂。像这个 ...[/quote]
    所以代码可以写成oldJson.call(response))
    这又该怎么写?数据还能再传导出去

    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    13

    主题

    416

    帖子

    723

    积分

    荣誉开发者

    Rank: 10Rank: 10Rank: 10

    积分
    723

    活跃会员热心会员突出贡献三好学生猫咪币纪念章中秋纪念章国庆纪念章荣誉开发者

    发表于 2021-8-27 21:25:09 | 显示全部楼层
    脚本体验师001 发表于 2021-8-27 20:17
    所以代码可以写成oldJson.call(response))
    这又该怎么写?数据还能再传导出去吗? ...

    是指oldJson.apply(this, arguments)这句可以等效换成oldJson.call(response)
    回复

    使用道具 举报

  • TA的每日心情
    擦汗
    2022-6-12 21:47
  • 签到天数: 21 天

    [LV.4]偶尔看看III

    11

    主题

    256

    帖子

    203

    积分

    高级工程师

    Rank: 6Rank: 6

    积分
    203

    中秋纪念章猫咪币纪念章活跃会员热心会员三好学生

    发表于 2021-8-27 21:31:58 | 显示全部楼层
    cxxjackie 发表于 2021-8-27 21:25
    是指oldJson.apply(this, arguments)这句可以等效换成oldJson.call(response)

    哦!!!
    回复

    使用道具 举报

  • TA的每日心情
    开心
    3 小时前
  • 签到天数: 56 天

    [LV.5]常住居民I

    352

    主题

    3110

    帖子

    3115

    积分

    管理员

    非物质文化遗产社会摇传承人

    Rank: 10Rank: 10Rank: 10

    积分
    3115

    猫咪币纪念章国庆纪念章中秋纪念章荣誉开发者家财万贯管理员

    发表于 2021-8-28 01:10:10 | 显示全部楼层

    cxxjackie 发表于 2021-8-25 21:22

    [md]我还是不喜欢Proxy这种劫持整个对象的做法,效率问题先放一边,看你的代码逻辑也显得非常复杂。像这个 ...

    我还是不喜欢Proxy这种劫持整个对象的做法,效率问题先放一边,看你的代码逻辑也显得非常复杂。像这个 ...[/quote]

    今天进行测试,我的proxy过滤存在严重问题,会导致网页的很多读写属性都查不到
    哥哥的方法很有可能是正确的,我明天测试

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表