微信 - Android 微信里自动调起系统浏览器访问页面

正常情况下,在微信里是无法自动调起系统浏览器访问页面的,但是通过如下所描述的 hack 方式,可以在 Android 微信里自动打开浏览器访问页面。

具体步骤为:

  1. 在微信里访问页面地址,比如http://windstone.cc/weixin/auto-launch-browser
  2. 后端或 Nginx 层判断是否是在 Android 微信里访问的,若是则返回如下的核心response header;否则正常返回页面内容。
  3. Android 微信接收到上面的response header,会自动调起系统浏览器,并在系统浏览器里再次访问页面地址http://windstone.cc/weixin/auto-launch-browser,此时后端或 Nginx 层判断出不是在 Android 微信里,则正常返回内容。

response header

HTTP/1.1 206 Partial Content
X-Powered-By: Express
Content-Type: text/plain; charset=utf-8
Accept-Ranges: bytes
Content-Range: bytes 0-1/1
Content-Disposition: attachment;filename=1579.apk
Content-Length: 0
ETag: W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"
Date: Tue, 17 Sep 2019 07:32:12 GMT
Proxy-Connection: keep-alive
1
2
3
4
5
6
7
8
9
10

核心response header代码片段

这个实现需要后端配合,或者在 Nginx 层里进行处理,要是想简单测试,可以通过 Charles 里的 Breakpoints 设置断点,在页面响应里将response header修改为上述response header,即可验证。

进一步优化

在上述的实现里,微信在拉起系统浏览器后,会显示一个白屏的页面,用户需要手动关闭这个页面,体验不好。

可以如下优化:

  1. 在微信里访问页面地址,比如http://windstone.cc/weixin/auto-launch-browser
  2. 后端或 Nginx 层正常返回页面内容,页面返回后正常执行、渲染。
  3. 页面渲染完成后,判断页面所在环境,若是在 Android 微信里,则通过window.location.href = 'http://windstone.cc/weixin/auto-launch-browser-api'请求后端接口,接口判断是在 Android 微信里后,返回上述的核心response header;此时,微信里打开的页面正常显示。
  4. Android 微信接收到上面的response header,会自动调起系统浏览器,并在系统浏览器里再次访问页面地址http://windstone.cc/weixin/auto-launch-browser-api,此时后端或 Nginx 层判断出不是在 Android 微信里,则通过 302 重定向到http://windstone.cc/weixin/auto-launch-browser

此方案还可以通过后端接口控制是否要调起浏览器,防止微信修复了该漏洞后出问题。

核心 response header 的解释

HTTP/1.1 206 Partial Content
X-Powered-By: Express
Content-Type: text/plain; charset=utf-8
Accept-Ranges: bytes
Content-Range: bytes 0-1/1
Content-Disposition: attachment;filename=1579.apk
Content-Length: 0
ETag: W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"
Date: Tue, 17 Sep 2019 07:32:12 GMT
Proxy-Connection: keep-alive
1
2
3
4
5
6
7
8
9
10
  • 206 Partial Content: 该成功状态响应代码表示请求已成功,并且主体包含所请求的数据区间,该数据区间是在请求的 Range 首部指定的。详见MDN - 206 Partial Contentopen in new window
  • X-Powered-By: Express: X-Powered-By是通用的非标准 HTTP 响应header(一般以X-作为前缀的header都是非标准的)。通常以特殊脚本技术默认包含在响应结构里。需要注意的是,这个头部可以被服务器禁用或修改。有些服务器选择不包含这个头部,甚至提供误导的信息以摆脱黑客,引导其转向一个特殊的技术/版本。因此,这个头部的信息时不足以为信的。更多详细信息,请参考Stack Overflow - What does “x-powered by” mean?open in new window
  • Accept-Ranges: bytes: 表示服务器支持Range请求,且支持的单位是bytes,也就意味着支持断点续传,可以并行多range进行下载。若是响应的是Accept-Ranges: none则表示不支持Range请求。
  • Content-Range: bytes 0-1/1: 表示响应的Range单位、第一个比特的位置、最后一个比特的位置和资源的总长度。此例里,Range单位是bytes,第一个比特的位置是0,最后一个比特的位置是1,资源的总长度是1
  • Content-Disposition: attachment;filename=1579.apk: 在常规的 HTTP 应答中,Content-Disposition消息头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。在 HTTP 场景中,第一个参数或者是inline(默认值,表示回复中的消息体会以页面的一部分或者整个页面的形式展示),或者是attachment(意味着消息体应该被下载到本地;大多数浏览器会呈现一个“保存为”的对话框,将filename的值预填为下载后的文件名,假如它存在的话)。更多详细信息,请参考MDN - Content-Dispositionopen in new window
  • ETag: W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk": 参考了其他几个第三方跳转服务的response header,其ETag的值也是W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"这个固定值。

因此,以上response header里,最核心的代码应该是Content-Disposition: attachment;filename=1579.apk

参考