Sunday, 11 October 2015

知乎 - DIY 问题重定向

今天我们来玩重定向游戏~

logout 知乎网页。 然后浏览 http://www.zhihu.com/question/20381025

就会看见 5 秒后将会跳转信息:


五秒过后, 跳去 www.zhihu.com/question/19694728?rf=20381025:



五秒过后, 又再跳去 www.zhihu.com/question/22447061?rf=19694728:



还没跳完,五秒过后, 又再跳去 www.zhihu.com/question/20135771?rf=22447061:



五秒过后, 跳去 www.zhihu.com/question/24531839?rf=20135771:



终于跳完 liao~ 是不是很好玩叻。当然你会觉得知乎 desgin 到酱真的是。。。

为什么我要强调 logout ? 因为 login 对知乎的重定向很大影响哦。如果没有 login, 以上所有的重定向都是交给了 js 来处理。 如果有 login,第一个重定向交给 302 处理 (所以你看不见第一页, 而是直接跳到第二页。

来证明一下,login 知乎后, 然后打开  http://www.zhihu.com/question/20381025



留意 URL bar, 是不是很奇怪叻, 直接 skip 掉 20381025 跳第二页。哦, 我还没讲上述 URL 的结构。

http://www.zhihu.com/question/问题_ID?rf=上一个重定向问题_ID

就这样, rf 猜到就是 referer 的简称。

不止这个特别,还会出现从什么问题跳转过来的信息。这就是 login 的特别之处。不但 skip 掉第一个 302, 而且会留意从哪里来。

那么如果想看回重定向之前的那页怎么办 ? 只要来得及按 "从问题 xxxxx  跳转而来" 就能看回之前那页, 并且不再重定向。这种设计,用户怎么可能懂呢 ?

login 的影响 inconsistent 的 behavior 还没完。"从问题 xxxxx  跳转而来" 本身就是 URL 加上 nr=1, 即是 no-redirect 的简称。 如果你没有 login, 你看不见 "从问题 xxxxx  跳转而来" 这行字也就算了,可是如果你手动把 URL 加上 nr=1, 比如 www.zhihu.com/question/20381025?nr=1:


还是会再跳的哦, 也就是说 login 不仅仅影响 302+js 重定向以及 block 掉 rf 信息, 而且也不允许 nr 的功能。

当然我是能想到 302+js 的初衷。302 省一次重定向, 然后又不想用户完全不懂第一页的题目, 所以只 302 静悄悄重定向一次, 接下来就用 "从问题 xxxxx  跳转而来" + js 方法重定向下一页。至于为什么要 login only 就感觉像 bug 多一点。

你可能看不懂我讲什么鬼, 简单一句, 如果是 http://www.zhihu.com/question/问题_ID?rf=上一个重定向问题_ID 链接就加上 &nr=1, 改成
http://www.zhihu.com/question/问题_ID?rf=上一个重定向问题_ID&nr=1 ,如果是 http://www.zhihu.com/question/问题_ID 链接就改成 http://www.zhihu.com/question/问题_ID?nr=1, 并且在 login 的情况下,链接是不会自动重定向的哦。

接下来让我们 DIY 两种重定向规则。

先准备 google chrome 浏览器。目标是自制 chrome extension。

准备两个 file, hello_inject.js 以及 manifest.json 放在同一个 directory/folder:



hello_inject.js 源码:

chrome.webRequest.onBeforeRequest.addListener(
        function(details) {
                if (details.url && /^https?:\/\/www.zhihu.com\/question\/\d+/i.test(details.url)) {
                        var url = details.url;
                        var paramName = "nr";
                        var paramValue = "1";

                        var splitAtAnchor = url.split('#');
                        url = splitAtAnchor[0];
                        var anchor = typeof splitAtAnchor[1] === 'undefined' ? '' : '#' + splitAtAnchor[1];
                        if (url.indexOf(paramName + "=") >= 0) {
                                var prefix = url.substring(0, url.indexOf(paramName));
                                var suffix = url.substring(url.indexOf(paramName));
                                suffix = suffix.substring(suffix.indexOf("=") + 1);
                                suffix = (suffix.indexOf("&") >= 0) ? suffix.substring(suffix.indexOf("&")) : "";
                                url = prefix + paramName + "=" + paramValue + suffix;
                        }
                        else {
                                if (url.indexOf("?") < 0)
                                        url += "?" + paramName + "=" + paramValue;
                                else
                                        url += "&" + paramName + "=" + paramValue;
                        }
 
                        console.log("injected url: " + url + anchor);
                        return {redirectUrl: url + anchor};
                }
        },
        {urls: ["<all_urls>"]},
        ["blocking"]);


manifest.json 源码:

{
  "name": "URL Injector",
  "version": "1.0",
  "description": "Injet url.",
  "background": {
    "scripts": ["hello_inject.js"]
     },
   "manifest_version": 2,
   "permissions": [ "contextMenus", "storage", "webRequest", "webRequestBlocking", "tabs", "http://*/*", "https://*/*" ]
}


然后去 chrome://extensions, 右手边打勾 Developer mode, 然后按 Load unpacked extension...:


选你刚才那个 directory/folder, 按 Open:



源码有 console.log 函数, 所以要看 log 就按 background page 打开 debug 窗口,然后选 Console Tab:



login 情况下,然后浏览 http://www.zhihu.com/question/20381025



yeah, inject nr=1 成功 :)。现在每一跳问题都会自动加上 nr=1 不再跳转。当然, 必须 login 哟。

如果你不要 print log 就前面加上 // 来 comment out console.log,
//console.log("redirect url: " + url + anchor);

如果更改储存 hello_inject.js 源码文件后, 必须在 chrome://extensions/ 页面, Ctrl+r 更新 take effect。

另一种玩法是选择直接强迫它 302 redirect。原理就是依赖 login + 把 rf 丢掉。因前面研究过,我们已经懂第一页是 302, 特征就是第一页没有 rf 然后接下来有 rf。

hello_inject.js 源码:
chrome.webRequest.onBeforeRequest.addListener(
        function(details) {
                if (details.url && /^https?:\/\/www.zhihu.com\/question\//i.test(details.url)) {
                        var url = details.url;
                        var base = url.substring(0, url.indexOf("?"));
                        var queryString = url.substring(url.indexOf("?") + 1);
                        var params = queryString.split("&");
                        var valid_param = 0;
                        var invalid_param = 0;
                        for (var i = 0; i < params.length; i++) {
                                var p = params[i].split("=");
                                if ((params[i] !== p[0]) && ( p[0] == "rf" )) {
                                        invalid_param+=1;
                                } else {
                                        if (valid_param !== 0) {
                                                base+=params[i];
                                                valid_param+=1;
                                        } else {
                                                base = base + "?" + params[i];
                                        }
                                }
                        }
                        if (invalid_param !== 0) {
                                console.log("redirect base: " + base);
                                return {redirectUrl: base};
                        }
                }
        },
        {urls: ["<all_urls>"]},
        ["blocking"]);


Ctrl+r 更新 extension, login 情况下浏览 http://www.zhihu.com/question/20381025 就会直接 skip 到最后一页 24531839:




如果 Inspect element 检测, 就能看到 302 -> 307(Internal Redirect) -> 302 -> 307 -> 302 -> 307 -> 302 -> 307 skip 到最后一页 200 OK。省下中间的 download。











No comments:

Post a Comment