李恒道 发表于 2021-8-25 18:43:11

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

# 前言

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劫持!!!

```javascript
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=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](data/attachment/forum/202108/25/182359hdivmetv5zgm16e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

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

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

![图片.png](data/attachment/forum/202108/25/182455t9bs71t1c8b9db5b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

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

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

# 实战

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


      });
}


```

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

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

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

```javascript
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](data/attachment/forum/202108/25/185046ynzy6hmyxannh3ah.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

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

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

![图片.png](data/attachment/forum/202108/25/185150c5878y22335qh2y8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

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

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

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

```javascript
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](data/attachment/forum/202108/25/185423z6y3637yfq8232y2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

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

![图片.png](data/attachment/forum/202108/25/185452ia1hi10roi5ih35c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

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

![图片.png](data/attachment/forum/202108/25/185629fnhxtyibqpu2sqim.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

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

```javascript
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](data/attachment/forum/202108/25/190802ckcyuu5ctcj9a1rw.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

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

我们使用的是response.json()

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

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

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

```javascript
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=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内进行判断

```javascript
                            target.oldfetch=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](data/attachment/forum/202108/25/192741xc7676nvvznwxx77.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

![图片.png](data/attachment/forum/202108/25/192748pp0pys56su2r6pra.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

![图片.png](data/attachment/forum/202108/25/192755wz6blxzej66xkgx8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

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

# 警告

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

# 结语

撒花~

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

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

```javascript
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)`)。箭头函数不只是写着好看的,很多时候多利用这些特性,可以解决很多问题。

李恒道 发表于 2021-8-25 22:13:00

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

Ne-21 发表于 2021-8-25 23:17:50

ggnb!!!!!!!!

李恒道 发表于 2021-8-26 09:08:25

Ne-21 发表于 2021-8-25 23:17
ggnb!!!!!!!!

哥哥牛逼!

脚本体验师001 发表于 2021-8-27 19:56:19

呜,二楼的哥哥代码似乎更简洁一点,但我想李大道大神是以教学为目的,出发点不一样吧,虽然我没看懂。试了一下二楼代码是可以直接拿来用的呀,哎呀我靠

脚本体验师001 发表于 2021-8-27 20:17:29

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

cxxjackie 发表于 2021-8-27 21:25:09

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

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

脚本体验师001 发表于 2021-8-27 21:31:58

cxxjackie 发表于 2021-8-27 21:25
是指oldJson.apply(this, arguments)这句可以等效换成oldJson.call(response)

哦!!!

李恒道 发表于 2021-8-28 01:10:10

cxxjackie 发表于 2021-8-25 21:22
我还是不喜欢Proxy这种劫持整个对象的做法,效率问题先放一边,看你的代码逻辑也显得非常复杂。像这个 ...

今天进行测试,我的proxy过滤存在严重问题,会导致网页的很多读写属性都查不到
哥哥的方法很有可能是正确的,我明天测试一下
页: [1] 2 3 4
查看完整版本: [油猴脚本开发指南]理解fetch劫持