1+ var USER_RULE_KEY = 'userRules' ;
12// Records which tabId's are active in the HTTPS Switch Planner (see
23// devtools-panel.js).
34var switchPlannerEnabledFor = { } ;
@@ -7,9 +8,26 @@ var switchPlannerEnabledFor = {};
78// rw / nrw stand for "rewritten" versus "not rewritten"
89var switchPlannerInfo = { } ;
910
11+ var getStoredUserRules = function ( ) {
12+ var oldUserRuleString = localStorage . getItem ( USER_RULE_KEY ) ;
13+ var oldUserRules = [ ] ;
14+ if ( oldUserRuleString ) {
15+ oldUserRules = JSON . parse ( oldUserRuleString ) ;
16+ }
17+ return oldUserRules ;
18+ } ;
1019var all_rules = new RuleSets ( ) ;
1120var wr = chrome . webRequest ;
21+ var loadStoredUserRules = function ( ) {
22+ var rules = getStoredUserRules ( ) ;
23+ var i ;
24+ for ( i = 0 ; i < rules . length ; ++ i ) {
25+ all_rules . addUserRule ( rules [ i ] ) ;
26+ }
27+ log ( 'INFO' , 'loaded ' + i + ' stored user rules' ) ;
28+ } ;
1229
30+ loadStoredUserRules ( ) ;
1331/*
1432for (var v in localStorage) {
1533 log(DBUG, "localStorage["+v+"]: "+localStorage[v]);
@@ -22,20 +40,23 @@ for (r in rs) {
2240}
2341*/
2442
25- // Add the HTTPS Everywhere icon to the URL address bar.
26- // TODO: Switch from pageAction to browserAction?
27- function displayPageAction ( tabId ) {
28- if ( tabId !== - 1 ) {
29- chrome . tabs . get ( tabId , function ( tab ) {
30- if ( typeof ( tab ) === "undefined" ) {
31- log ( DBUG , "Not a real tab. Skipping showing pageAction." ) ;
32- }
33- else {
34- chrome . pageAction . show ( tabId ) ;
35- }
36- } ) ;
43+
44+ var addNewRule = function ( params , cb ) {
45+ if ( all_rules . addUserRule ( params ) ) {
46+ // If we successfully added the user rule, save it in local
47+ // storage so it's automatically applied when the extension is
48+ // reloaded.
49+ var oldUserRules = getStoredUserRules ( ) ;
50+ // TODO: there's a race condition here, if this code is ever executed from multiple
51+ // client windows in different event loops.
52+ oldUserRules . push ( params ) ;
53+ // TODO: can we exceed the max size for storage?
54+ localStorage . setItem ( USER_RULE_KEY , JSON . stringify ( oldUserRules ) ) ;
55+ cb ( true ) ;
56+ } else {
57+ cb ( false ) ;
3758 }
38- }
59+ } ;
3960
4061function AppliedRulesets ( ) {
4162 this . active_tab_rules = { } ;
@@ -115,21 +136,13 @@ function onBeforeRequest(details) {
115136 // If no rulesets could apply, let's get out of here!
116137 if ( rs . length === 0 ) { return ; }
117138
118- if ( details . requestId in redirectCounter ) {
119- redirectCounter [ details . requestId ] += 1 ;
120- log ( DBUG , "Got redirect id " + details . requestId +
121- ": " + redirectCounter [ details . requestId ] ) ;
122-
123- if ( redirectCounter [ details . requestId ] > 9 ) {
124- log ( NOTE , "Redirect counter hit for " + canonical_url ) ;
125- urlBlacklist [ canonical_url ] = true ;
126- var hostname = uri . hostname ( ) ;
127- domainBlacklist [ hostname ] = true ;
128- log ( WARN , "Domain blacklisted " + hostname ) ;
129- return ;
130- }
131- } else {
132- redirectCounter [ details . requestId ] = 0 ;
139+ if ( redirectCounter [ details . requestId ] >= 8 ) {
140+ log ( NOTE , "Redirect counter hit for " + canonical_url ) ;
141+ urlBlacklist [ canonical_url ] = true ;
142+ var hostname = uri . hostname ( ) ;
143+ domainBlacklist [ hostname ] = true ;
144+ log ( WARN , "Domain blacklisted " + hostname ) ;
145+ return null ;
133146 }
134147
135148 var newuristr = null ;
@@ -321,103 +334,75 @@ function switchPlannerDetailsHtmlSection(tab_id, rewritten) {
321334function onCookieChanged ( changeInfo ) {
322335 if ( ! changeInfo . removed && ! changeInfo . cookie . secure ) {
323336 if ( all_rules . shouldSecureCookie ( changeInfo . cookie , false ) ) {
324- var cookie = { name :changeInfo . cookie . name , value :changeInfo . cookie . value ,
325- domain :changeInfo . cookie . domain , path :changeInfo . cookie . path ,
337+ var cookie = { name :changeInfo . cookie . name ,
338+ value :changeInfo . cookie . value ,
339+ path :changeInfo . cookie . path ,
326340 httpOnly :changeInfo . cookie . httpOnly ,
327341 expirationDate :changeInfo . cookie . expirationDate ,
328- storeId :changeInfo . cookie . storeId } ;
329- cookie . secure = true ;
330- // FIXME: What is with this url noise? are we just supposed to lie?
331- if ( cookie . domain [ 0 ] == "." ) {
332- cookie . url = "https://www" + cookie . domain + cookie . path ;
342+ storeId :changeInfo . cookie . storeId ,
343+ secure : true } ;
344+
345+ // Host-only cookies don't set the domain field.
346+ if ( ! changeInfo . cookie . hostOnly ) {
347+ cookie . domain = changeInfo . cookie . domain ;
348+ }
349+
350+ // The cookie API is magical -- we must recreate the URL from the domain and path.
351+ if ( changeInfo . cookie . domain [ 0 ] == "." ) {
352+ cookie . url = "https://www" + changeInfo . cookie . domain + cookie . path ;
333353 } else {
334- cookie . url = "https://" + cookie . domain + cookie . path ;
354+ cookie . url = "https://" + changeInfo . cookie . domain + cookie . path ;
335355 }
336356 // We get repeated events for some cookies because sites change their
337357 // value repeatedly and remove the "secure" flag.
338358 log ( DBUG ,
339- "Securing cookie " + cookie . name + " for " + cookie . domain + ", was secure=" + changeInfo . cookie . secure ) ;
359+ "Securing cookie " + cookie . name + " for " + changeInfo . cookie . domain + ", was secure=" + changeInfo . cookie . secure ) ;
340360 chrome . cookies . set ( cookie ) ;
341361 }
342362 }
343363}
344364
345- // This event is needed due to the potential race between cookie permissions
346- // update and cookie transmission (because the cookie API is non-blocking).
347- // Without this function, an aggressive attacker could race to steal a not-yet-secured
348- // cookie if they controlled & could redirect the user to a non-SSL subdomain.
349- // WARNING: This is a very hot function.
350- function onBeforeSendHeaders ( details ) {
351- // TODO: Verify this with wireshark
352- for ( var h in details . requestHeaders ) {
353- if ( details . requestHeaders [ h ] . name == "Cookie" ) {
354- // Per RFC 6265, Chrome sends only ONE cookie header, period.
355- var uri = new URI ( details . url ) ;
356- var host = uri . hostname ( ) ;
357-
358- var newCookies = [ ] ;
359- var cookies = details . requestHeaders [ h ] . value . split ( ";" ) ;
360-
361- for ( var c in cookies ) {
362- // Create a fake "nsICookie2"-ish object to pass in to our rule API:
363- var fake = { domain :host , name :cookies [ c ] . split ( "=" ) [ 0 ] } ;
364- // XXX I have no idea whether the knownHttp parameter should be true
365- // or false here. We're supposedly inside a race condition or
366- // something, right?
367- var ruleset = all_rules . shouldSecureCookie ( fake , false ) ;
368- if ( ruleset ) {
369- activeRulesets . addRulesetToTab ( details . tabId , ruleset ) ;
370- log ( INFO , "Woah, we lost the race on updating a cookie: " + details . requestHeaders [ h ] . value ) ;
365+ function onBeforeRedirect ( details ) {
366+ // Catch HTTPs -> HTTP redirect loops, ignoring about:blank, HTTPS 302s, etc.
367+ if ( details . redirectUrl . substring ( 0 , 7 ) === "http://" ) {
368+ if ( details . requestId in redirectCounter ) {
369+ redirectCounter [ details . requestId ] += 1 ;
370+ log ( DBUG , "Got redirect id " + details . requestId +
371+ ": " + redirectCounter [ details . requestId ] ) ;
371372 } else {
372- newCookies . push ( cookies [ c ] ) ;
373+ redirectCounter [ details . requestId ] = 1 ;
373374 }
374- }
375- details . requestHeaders [ h ] . value = newCookies . join ( ";" ) ;
376- log ( DBUG , "Got new cookie header: " + details . requestHeaders [ h ] . value ) ;
377-
378- // We've seen the one cookie header, so let's get out of here!
379- break ;
380375 }
381- }
382-
383- return { requestHeaders :details . requestHeaders } ;
384- }
385-
386- function onResponseStarted ( details ) {
387-
388- // redirect counter workaround
389- // TODO: Remove this code if they ever give us a real counter
390- if ( details . requestId in redirectCounter ) {
391- delete redirectCounter [ details . requestId ] ;
392- }
393376}
394377
395378wr . onBeforeRequest . addListener ( onBeforeRequest , { urls : [ "https://*/*" , "http://*/*" ] } , [ "blocking" ] ) ;
396379
397- // This watches cookies sent via HTTP.
398- // We do *not* watch HTTPS cookies -- they're already being sent over HTTPS -- yay!
399- wr . onBeforeSendHeaders . addListener ( onBeforeSendHeaders , { urls : [ "http://*/*" ] } ,
400- [ "requestHeaders" , "blocking" ] ) ;
401-
402- wr . onResponseStarted . addListener ( onResponseStarted ,
403- { urls : [ "https://*/*" , "http://*/*" ] } ) ;
380+ // Try to catch redirect loops on URLs we've redirected to HTTPS.
381+ wr . onBeforeRedirect . addListener ( onBeforeRedirect , { urls : [ "https://*/*" ] } ) ;
404382
405383
406384// Add the small HTTPS Everywhere icon in the address bar.
407385// Note: We can't use any other hook (onCreated, onActivated, etc.) because Chrome resets the
408386// pageActions on URL change. We should strongly consider switching from pageAction to browserAction.
409387chrome . tabs . onUpdated . addListener ( function ( tabId , changeInfo , tab ) {
410- displayPageAction ( tabId ) ;
388+ if ( changeInfo . status === "loading" ) {
389+ chrome . pageAction . show ( tabId ) ;
390+ }
411391} ) ;
412392
413393// Pre-rendered tabs / instant experiments sometimes skip onUpdated.
414394// See http://crbug.com/109557
415395chrome . tabs . onReplaced . addListener ( function ( addedTabId , removedTabId ) {
416- displayPageAction ( addedTabId ) ;
396+ chrome . tabs . get ( addedTabId , function ( tab ) {
397+ if ( typeof ( tab ) === "undefined" ) {
398+ log ( DBUG , "Not a real tab. Skipping showing pageAction." ) ;
399+ } else {
400+ chrome . pageAction . show ( addedTabId ) ;
401+ }
402+ } ) ;
417403} ) ;
418404
419- // Listen for cookies set/updated and secure them if applicable. This function is async/nonblocking,
420- // so we also use onBeforeSendHeaders to prevent a small window where cookies could be stolen.
405+ // Listen for cookies set/updated and secure them if applicable. This function is async/nonblocking.
421406chrome . cookies . onChanged . addListener ( onCookieChanged ) ;
422407
423408function disableSwitchPlannerFor ( tabId ) {
0 commit comments