📜 ⬆️ ⬇️

Google Chrome extensions: cookies and HTTP requests

Continuing a series of publications on the development of extensions Google Chrome. True, it ends with this post, because at the moment I no longer have anything to tell about this. What will happen next - we'll see.
In this topic, I will talk about street magic casting using the example of cookies and low-level HTTP requests. The material of this post is completely based on my first expansion, which subsequently acquired a good functionality.

Cookies


Let's start with cookies. To complete the work with them, it is enough to have the following line in the manifest:
{ ... "permissions": [ "cookies", ... ], ... } 

The extension is given full access to all cookies. Even those that are installed temporarily in the incognito window, if there is a corresponding check mark for access in the browser extensions control window. I note that the API seemed to me rather inconvenient. But you can figure it out, and everything works fine.
Based on this, an extension was developed to store and switch between different sessions on sites. Initially, the goal and idea was to facilitate multi-breeding in social games, but then I positioned it as a universal tool.

How it works?
The browser API has the following methods: chrome.cookies.get(details, callback) for getting data about one cookie, a similar getAll method for getting data about all cookies (with the same parameters, but details work as a filter, it turned out to be very convenient) , and chrome.cookies.set(details, callback) . I will not popularly describe the parameters and use, you can read about it in the documentation . It’s better to go straight to the point.
Basic methods:
 function deleteCookies() { //  cookies   for (var i = 0; i < .length; i++) { try { var d = [i]; var u = ((d.secure) ? "https://" : "http://") + d.domain + d.path; chrome.cookies.remove({ url: u, name: d.name, storeId: d.storeId }); } catch (e) { console.error("Error catched deleting cookie:\n" + e.description); } } } function setCookies(c) { // cookies   for (var i = 0; i < c.length; i++) { try { var d = c[i]; var b = { url: ((d.secure) ? "https://" : "http://") + d.domain, name: d.name, storeId: d.storeId, value: d.value, path: = d.path, secure: = d.secure, httpOnly: = d.httpOnly, }; if (!d.hostOnly) { b.domain = d.domain; } if (!d.session) { b.expirationDate = d.expirationDate; } chrome.cookies.set(b); } catch (e) { console.error("Error setting cookie:\n" + e.description) } } } 

Just want to clarify about storeId. Cookies in the browser are stored in such containers. There is even a chrome.cookies.getAllCookieStores(callback) method, in response to which something like this is returned:
 [{ id: 0, tabIds: [1,2,3,4], },] 
But always returns 1 or 2 objects: one normal, and one for the incognito window (if it is open, and the extension is given access to this window). Judging by the presence of the tabIds parameter, it was either planned and forgotten, or else the possibility of managing these stores and linking tabs to a specific store is planned. If it already worked, it would help in the implementation of the idea, which will be discussed in the next section.

So, there are basic methods, you can do profile switching. Below is the code for the Facebook example.
 var profiles = JSON.parse(localStorage.profiles); //    var inuse = localStorage.inuse; //  var cookies = ['facebook.com','secure.facebook.com','on.fb.me']; //,     cookies function newProfile(){ //      cookies var ii = 0; // cookies   callback,       ,    var initer = function(cii){ chrome.cookies.getAll({domain:cookies[cii]}, function (f){ var nc = profiles.length-1; profiles[nc].cookies = profiles[nc].cookies.concat(f); if (cii<(cookies.length-1)){ cii++; initer(cii); } else{ saveSettings(); //... } }); } chrome.cookies.getAll({domain:cookies[0]}, function (f){ var nc = profiles.length; profiles[nc] = {title:'New profile '+nc,cookies:f}; inuse = nc; if (ii<(cookies.length-1)){ ii++; initer(ii); } else{ saveSettings(); //... } }); } function switchProfile(d) { //  if (profiles[d] != undefined && profiles[d] != null && d != inuse) { //cookies, ,  .        cookies //     var deleter = function(cii){ chrome.cookies.getAll({domain:cookies[cii]}, function (f){ deleteCookies(f); if (cii < (cookies.length-1)){ cii++; deleter(cii); } else{ restoreCookies(profiles[d].cookies); inuse = d; saveSettings(); //...+ } }); } deleter(0); } } 

The code above is slightly simplified for demonstration. In fact, the extension supports several sites, because the processing functions have the appropriate appearance. I think the functions themselves do not make sense to explain, everything is clear from the comments.
The extension does not react to changes in stored cookies in the browser, it simply stores the state at the time the profile is created. But in the API there are corresponding events to which you can subscribe and somehow handle them. It should also be noted that in the case of the presence of protection on the website or the possibility of closing all previous sessions, these cookies may stop working after some time, if the IP address is changed or sessions are deleted. But for many services and social networks, at the mark of permanent authorization, everything works indefinitely.
Approximately in this form, my solution works to this day. And there was nothing to add for a long time, if not for one event.
')

HTTP requests


It so happened that the owner was limited to access to one resource. Moreover, the filtering took place on the Referer header. In search of a solution, I learned about the functions of the webRequest API. They allow you to view and manage all requests that pass in the browser.
Let's start with the manifest.
 { ... "permissions": [ "webRequest", "webRequestBlocking" ], ... } 

The first parameter provides the ability to add an event handler to request events, the second, in addition, also allows you to modify or block them.
Refinement
Now the API description contains information about declarativeWebRequest . Judging by the descriptions, it provides the same functionality, but it should be much faster, since the rules are executed directly in the browser, and not in JavaScript. But so far (and already for a long time) it is in the beta stage.
Regarding speed, my extension iterates through and, if necessary, modifies some variables and request headers, and catches all requests. I did not notice any noticeable brakes from this.
Regarding the publication
The extension in which I first used this API initially worked without it. After the first update, where I turned on these functions, he was sent to manual moderation, which took about two weeks. After any problems, even with the addition of similar functionality to another extension, did not arise. Therefore, I am not sure whether such measures are being taken now.

The API provides the ability to assign handlers to each event from the life cycle of an HTTP request and to do almost any actions with the request. What is important, besides other information, the initiator of the request is also passed in the parameters: tab ID, pages, etc. On the basis of this, the idea was born of making it possible to simultaneously hold several profiles in different tabs. After all, in each request it is enough to replace the sent cookies with the necessary ones from the profile.
No sooner said than done!
Let's assign the appropriate listener to the onBeforeSendHeaders event and replace the headers with the necessary ones.
 var tabs = {}; //       function openProfile(id,url){ //  if (url.indexOf('#')>-1) url = url.substr(0,url.indexOf('#')+1) + encodeURIComponent(url.substr(url.indexOf('#')+1)); // , -          ,    loadSettings(); chrome.tabs.create({url:url},function(r){ //         if (r) tabs[r.id] = new Array(tinuse,id,profiles[id].cookies.slice()); }); } chrome.webRequest.onBeforeSendHeaders.addListener(function(d){ if (d.tabId){ //   td = d.tabId; if (tabs[td]){ //     td = tabs[td]; for (var i = 0; i < d.requestHeaders.length; i++){ if (d.requestHeaders[i].name=='Cookie'){ var cks = d.requestHeaders[i].value.split('; '); // Cookies    for (var j = 0; j < cks.length; j++){ cks[j] = cks[j].split('='); for (var k = 0; k < td[2].length; k++){ if (td[2][k].name==cks[j][0]){ // cks[j][1] = td[2][k].value; break; } } cks[j] = cks[j].join('='); } d.requestHeaders[i].value = cks.join('; '); //  cookies } } } } return {requestHeaders: d.requestHeaders}; //    },{ urls: ["<all_urls>"] //        .    ,         },[ "blocking", //    "requestHeaders" //   ]); 

At first, it was such an option. As it turned out, this is not enough. Due to inactivity (How much time can elapse after the profile was last used? Moreover, cookies are stored which were at the time of creation and are not updated during the work.) The session is outdated and is redirected to the authorization page, where, nevertheless, authorization happens automatically, but a new session is created. Therefore, it is necessary to handle this.
 chrome.webRequest.onHeadersReceived.addListener(function(d){ if (d.tabId){ td = d.tabId; if (tabs[td]){ td = tabs[td]; //Cookies      : //name: "Set-Cookie" //value: "cookiename=value; expires=Sun, 22-Dec-2013 03:31:23 GMT; path=/; domain=.domain.com" for (var i = 0; i < d.responseHeaders.length; i++){ if (d.responseHeaders[i].name=='Set-Cookie'){ var sk = d.responseHeaders[i].value.split('; '); sk[0] = sk[0].split('='); var ck = {name:sk[0][0],value:sk[0][1]}; for (var j = 1; j < sk.length; j++){ sk[j] = sk[j].split('='); ck[sk[j][0]] = sk[j][1]; } for (var k = 0; k < td[2].length; k++){ if (td[2][k].name==ck.name && td[2][k].path==ck.path && td[2][k].domain==ck.domain){ //   cookie,   td[2][k].value = ck.value; break; } } //    ,      cookies   ,          d.responseHeaders.splice(i,1); i--; } } } } return {responseHeaders: d.responseHeaders}; },{ urls: ["<all_urls>"] },[ "blocking", "responseHeaders" ]); 

Now everything works. Initially, I thought of updating cookies in the stored profile data, but then it would have been a mess. You can open it in a new tab, and then log in under a different profile, something else to do, and everything will be lost. Therefore, it was decided to create a temporary copy at work. But once such a thing - the same data must then be deleted. It also does not hurt to handle the opening of new tabs from the current one.
 chrome.tabs.onCreated.addListener(function(tab){ if (tab.openerTabId){ //       - -  if (tabs[tab.openerTabId]){ tabs[tab.id] = tabs[tab.openerTabId]; } } }); chrome.tabs.onRemoved.addListener(function(tabId, removeInfo) { //   .  -    ID –  if (tabs[tabId]) delete tabs[tabId]; }); 

In the same form it works in my extension.
A more global idea also emerged during development. Write an extension for creating such isolated tabs with initially empty cookies and processing all events there completely. Such self-insertion, similar in functionality to the incognito window. But I haven’t done it yet through the complete lack of the browser’s ability to visually highlight such tabs among the rest. Fastening is not the case; I also see no reason to produce windows. Therefore, it is still in the air as an idea.

These are the "miracles" you can do using the tools offered by the browser. Considering the simplicity and the tools used to develop - I never cease to admire Chrome in comparison with Firefox.
By the way, the ability to create and bind to the tabs cookieStore, which I wrote above, would greatly help in the implementation of the proposed functionality. I really hope that once it will be, as well as the ability to visually distinguish the tabs. For example, highlight them in color, as is done in IE.
And lastly. Do not think for advertising. If anyone is interested, the extension I mentioned in this post is “VK + Switcher”. I did not publish source codes anywhere, but if you want to dig out, you can install and view the structure in the browser folder with extensions.

Source: https://habr.com/ru/post/171959/


All Articles