preface

This article mainly records how the browser synchronizes the WkWebView with the native cookie in the study of the browser’s network blocking.

Why do you need to synchronize cookies?

In the WebKit kernel, network requests are made in a separate process, and the WebKit kernel has its own cookie mechanism. There is also a native cookie management mechanism in iOS called NSHTTPCookieStorage. Since WebKit cookies and native cookies are two separate mechanisms, the cookies between them are not synchronized. So when we intercept a web request from WebKit via NSURLProtol, all the cookies that are generated by the web request, are stored in NSHTTPCookieStorage, However, NSHTTPCookieStorage does not synchronize with WebKit cookies, which causes js to be unable to obtain related cookies through document.cookie. In addition, the cookies set by JS are stored in WebKit cookies, and this part of cookies cannot be obtained when the native network request is initiated.

Therefore, we need some mechanism to synchronize WebKit cookies with the cookies in NSHTTPCookieStorage.

Cookie synchronization of the browser

Sync WebKit cookies to native cookies

Through studying the JS code of cookie synchronization of browser, it is found that it is mainly synchronized through the set and GET methods of Documment. Cookie in Hook JS. The specific code is as follows:

Object.defineProperty(document.'cookie', {
    get: function() {
        // Retrieve the cookie from the cookie list
        var cookieList = cookieNameAndValueList();
        return cookieList;
    },
    set: function(cookieString) {
        if (typeofcookieString ! = ='string') {
            return;
        }
        // Store the cookie in the cookie List
        getCookieAndAddToCookieListFromString(cookieString);
        var message = {};
        message['cookie'] = cookieString;
        // Send the cookie to the native
        __B_Cookie_Handle__.postMessage(message);
    },
    configurable: true
});
Copy the code

The whole idea is to maintain a cookie list by yourself through the set and GET methods of Hook Documment. Cookie. When you set a new cookie, you will store the new cookie in the cookie list you maintain. And the cookie is synchronized to the native via postMessage. When a cookie is obtained, it is returned from the cookie list it maintains.

Sync native cookies to WebKit cookies

The browser will check the native HTTP Response header and call the [xxxWebView bSetCookie:] method to notify if there is a set-cookie field. In this method, the following JS statement will be executed

if (window.b_Notify){
    b_Notify('seewo.com'.'csrfToken=WXcGJ7BxBBNb05gDICx6KNk8; path=/')}Copy the code

The b_Notify method is called to synchronize the cookie to WebKit, and b_Notify is implemented as follows:

window['b_Notify'] = function(host, cookieString) {
    // Determine whether the data is a cookie under the same domain name
    if (isSuffix(document.location.hostname, host)) {
        if (typeof cookieString == 'string') {
            // If there are more than one cookie, it will be divided according to,
            var cookieStringList = cookieString.split(', ');
            var length = cookieStringList.length;
            if (length > 0) {
                for (var index = 0; index < length; index++) {
                    var cookieString = cookieStringList[index];
                    // Since cookie Expires is set to Expires=Wed, 21 Oct 2015 07:28:00 GMT; It also contains ", ",
                    / / so if judge matching to cookieString containing the Sun and other fields, is the current and the next field are combined, form a complete cookies
                    if (cookieString.match(/=Sun|=Mon|=Tue|=Wed|=Thu|=Fri|=Sat/) = =null && (index < length - 1)) {
                        // After converting the cookie String into obj, save it to the cookie list maintained by yourself
                        saveCookieStringToList(cookieString);
                    } else {
                        saveCookieStringToList(cookieString + ', ' + cookieStringList[index + 1]);
                        index++;
                    }
                }
            }
        }
    }

    // Synchronize the cookie to all subframes
    var subFrames = document.querySelectorAll('iframe');
    if(! subFrames) {return;
    }
    var length = subFrames.length;
    var index;
    if (length > 0) {
        for (index = 0; index < length; index++) {
            var message = {};
            message['syss_info'] = host;
            message['sck_info'] = cookieString;
            subFrames[index].contentWindow.postMessage(message, The '*'); }}};Copy the code

B_Notify’s will analyze the cookie string obtained from the native and save it in its own maintained cookie list. The cookie is synchronized to all subframes.

To sum up, in the browser, through hook document set method, the cookie set in JS will be synchronized to the native. By injecting the b_Notify method in JS, when the native listener has set-cookie field in the response of the request, the native directly calls the injected b_Notify method to synchronize the Cookie to Webkit.

ManFrame cookie synchronization

With the above analysis, WebKit cookies and native cookies can now be synchronized. But there is one boundary condition that is missing. Let’s just sort out the process. As shown in the figure below:

As you can see from the figure above, if the response of the main frame is set with a set-cookie field, logic would try to call b_Notify, but since the main Frame has not been loaded by WebKit at this point, B_Notify is not actually injected into WebKit yet, so there is no way to synchronize the native cookie to WebKit via b_Notify. To solve this problem, when the browser executes the cookie synchronization associated with JS, The JS layer will actively initiate a special network request. After native intercepting the special network request, the cookie of the URL specified in the special network request will be returned. The specific code is as follows:


function() {

    function asyncGetCookieFromNative(A) {
        /** * Ignore part of the code */
    }
    function syncGetCookieFromNative(B) {
        var reqeust = new XMLHttpRequest();
        if(reqeust ! =null) {
            reqeust.open('GET', B, false);
            reqeust.send();
            if (reqeust.status == 200) {
                var responseText = reqeust.responseText;
                if (typeof responseText == 'undefined' || responseText.length == 0) {
                    return;
                }
                var cookieList = JSON.parse(responseText);
                if (typeof cookieList === 'object') { addCookieList(cookieList); }}}}function getCookieFromNative(isAsync) {
        var protocol = document.location.protocol;
        var host = document.location.host;
        if (typeofprotocol ! = ='string' || typeofhost ! = ='string') {
            return;
        }
        // Create a special network request with the current page's href address following the network request
        if (isAsync) {
            asyncGetCookieFromNative(protocol + '/ /' + host + '/9CB4F2575FDD4C5BA05A63E96FC96E70/? ' + document.location.href);
        } else {
            syncGetCookieFromNative(protocol + '/ /' + host + '/9CB4F2575FDD4C5BA05A63E96FC96E70/? ' + document.location.href); }}/** * Ignore part of the code */
    getCookieFromNative(false);
} ()

Copy the code

Because native can intercept all of its network request, so when its launch with special characters (9 cb4f2575fdd4c5ba05a63e96fc96e70) blocked by native, native can direct access to the url in the search, After obtaining cookies in NSHTTPCookieStorage through URL, return the cookies to Webkit through response. In this way, WebKit can actively pull the cookie from the native.