1+ // Records which tabId's are active in the HTTPS Switch Planner (see
2+ // devtools-panel.js).
3+ var switchPlannerEnabledFor = { } ;
4+ // Detailed information recorded when the HTTPS Switch Planner is active.
5+ // Structure is:
6+ // switchPlannerInfo[tabId]["rw"/"nrw"][resource_host][active_content][url];
7+ // rw / nrw stand for "rewritten" versus "not rewritten"
8+ var switchPlannerInfo = { } ;
19
210var all_rules = new RuleSets ( ) ;
311var wr = chrome . webRequest ;
@@ -141,6 +149,16 @@ function onBeforeRequest(details) {
141149 newuristr = finaluri . toString ( ) ;
142150 }
143151
152+ // In Switch Planner Mode, record any non-rewriteable
153+ // HTTP URIs by parent hostname, along with the resource type.
154+ if ( switchPlannerEnabledFor [ details . tabId ] && uri . protocol ( ) !== "https" ) {
155+ writeToSwitchPlanner ( details . type ,
156+ details . tabId ,
157+ canonical_host ,
158+ details . url ,
159+ newuristr ) ;
160+ }
161+
144162 if ( newuristr ) {
145163 log ( DBUG , "Redirecting from " + details . url + " to " + newuristr ) ;
146164 return { redirectUrl : newuristr } ;
@@ -149,6 +167,157 @@ function onBeforeRequest(details) {
149167 }
150168}
151169
170+
171+ // Map of which values for the `type' enum denote active vs passive content.
172+ // https://developer.chrome.com/extensions/webRequest.html#event-onBeforeRequest
173+ var activeTypes = { stylesheet : 1 , script : 1 , object : 1 , other : 1 } ;
174+ // We consider sub_frame to be passive even though it can contain JS or Flash.
175+ // This is because code running the sub_frame cannot access the main frame's
176+ // content, by same-origin policy. This is true even if the sub_frame is on the
177+ // same domain but different protocol - i.e. HTTP while the parent is HTTPS -
178+ // because same-origin policy includes the protocol. This also mimics Chrome's
179+ // UI treatment of insecure subframes.
180+ var passiveTypes = { main_frame : 1 , sub_frame : 1 , image : 1 , xmlhttprequest : 1 } ;
181+
182+ // Record a non-HTTPS URL loaded by a given hostname in the Switch Planner, for
183+ // use in determining which resources need to be ported to HTTPS.
184+ function writeToSwitchPlanner ( type , tab_id , resource_host , resource_url , rewritten_url ) {
185+ var rw = "rw" ;
186+ if ( rewritten_url == null )
187+ rw = "nrw" ;
188+
189+ var active_content = 0 ;
190+ if ( activeTypes [ type ] ) {
191+ active_content = 1 ;
192+ } else if ( passiveTypes [ type ] ) {
193+ active_content = 0 ;
194+ } else {
195+ log ( WARN , "Unknown type from onBeforeRequest details: `" + type + "', assuming active" ) ;
196+ active_content = 1 ;
197+ }
198+
199+ if ( ! switchPlannerInfo [ tab_id ] ) {
200+ switchPlannerInfo [ tab_id ] = { } ;
201+ switchPlannerInfo [ tab_id ] [ "rw" ] = { } ;
202+ switchPlannerInfo [ tab_id ] [ "nrw" ] = { } ;
203+ }
204+ if ( ! switchPlannerInfo [ tab_id ] [ rw ] [ resource_host ] )
205+ switchPlannerInfo [ tab_id ] [ rw ] [ resource_host ] = { } ;
206+ if ( ! switchPlannerInfo [ tab_id ] [ rw ] [ resource_host ] [ active_content ] )
207+ switchPlannerInfo [ tab_id ] [ rw ] [ resource_host ] [ active_content ] = { } ;
208+
209+ switchPlannerInfo [ tab_id ] [ rw ] [ resource_host ] [ active_content ] [ resource_url ] = 1 ;
210+ }
211+
212+ // Return the number of properties in an object. For associative maps, this is
213+ // their size.
214+ function objSize ( obj ) {
215+ if ( typeof obj == 'undefined' ) return 0 ;
216+ var size = 0 , key ;
217+ for ( key in obj ) {
218+ if ( obj . hasOwnProperty ( key ) ) size ++ ;
219+ }
220+ return size ;
221+ }
222+
223+ // Make an array of asset hosts by score so we can sort them,
224+ // presenting the most important ones first.
225+ function sortSwitchPlanner ( tab_id , rewritten ) {
226+ var asset_host_list = [ ] ;
227+ if ( typeof switchPlannerInfo [ tab_id ] === 'undefined' ||
228+ typeof switchPlannerInfo [ tab_id ] [ rewritten ] === 'undefined' ) {
229+ return [ ] ;
230+ }
231+ var tabInfo = switchPlannerInfo [ tab_id ] [ rewritten ] ;
232+ for ( var asset_host in tabInfo ) {
233+ var ah = tabInfo [ asset_host ] ;
234+ var activeCount = objSize ( ah [ 1 ] ) ;
235+ var passiveCount = objSize ( ah [ 0 ] ) ;
236+ var score = activeCount * 100 + passiveCount ;
237+ asset_host_list . push ( [ score , activeCount , passiveCount , asset_host ] ) ;
238+ }
239+ asset_host_list . sort ( function ( a , b ) { return a [ 0 ] - b [ 0 ] } ) ;
240+ return asset_host_list ;
241+ }
242+
243+ // Format the switch planner output for presentation to a user.
244+ function switchPlannerSmallHtmlSection ( tab_id , rewritten ) {
245+ var asset_host_list = sortSwitchPlanner ( tab_id , rewritten ) ;
246+ if ( asset_host_list . length == 0 ) {
247+ return "<b>none</b>" ;
248+ }
249+
250+ var output = "" ;
251+ for ( var i = asset_host_list . length - 1 ; i >= 0 ; i -- ) {
252+ var host = asset_host_list [ i ] [ 3 ] ;
253+ var activeCount = asset_host_list [ i ] [ 1 ] ;
254+ var passiveCount = asset_host_list [ i ] [ 2 ] ;
255+
256+ output += "<b>" + host + "</b>: " ;
257+ if ( activeCount > 0 ) {
258+ output += activeCount + " active" ;
259+ if ( passiveCount > 0 )
260+ output += ", " ;
261+ }
262+ if ( passiveCount > 0 ) {
263+ output += passiveCount + " passive" ;
264+ }
265+ output += "<br/>" ;
266+ }
267+ return output ;
268+ }
269+
270+ function switchPlannerRenderSections ( tab_id , f ) {
271+ return "Unrewritten HTTP resources loaded from this tab (enable HTTPS on " +
272+ "these domains and add them to HTTPS Everywhere):<br/>" +
273+ f ( tab_id , "nrw" ) +
274+ "<br/>Resources rewritten successfully from this tab (update these " +
275+ "in your source code):<br/>" +
276+ f ( tab_id , "rw" ) ;
277+ }
278+
279+ function switchPlannerSmallHtml ( tab_id ) {
280+ return switchPlannerRenderSections ( tab_id , switchPlannerSmallHtmlSection ) ;
281+ }
282+
283+ function linksFromKeys ( map ) {
284+ if ( typeof map == 'undefined' ) return "" ;
285+ var output = "" ;
286+ for ( var key in map ) {
287+ if ( map . hasOwnProperty ( key ) ) {
288+ output += "<a href='" + key + "'>" + key + "</a><br/>" ;
289+ }
290+ }
291+ return output ;
292+ }
293+
294+ function switchPlannerDetailsHtml ( tab_id ) {
295+ return switchPlannerRenderSections ( tab_id , switchPlannerDetailsHtmlSection ) ;
296+ }
297+
298+ function switchPlannerDetailsHtmlSection ( tab_id , rewritten ) {
299+ var asset_host_list = sortSwitchPlanner ( tab_id , rewritten ) ;
300+ var output = "" ;
301+
302+ for ( var i = asset_host_list . length - 1 ; i >= 0 ; i -- ) {
303+ var host = asset_host_list [ i ] [ 3 ] ;
304+ var activeCount = asset_host_list [ i ] [ 1 ] ;
305+ var passiveCount = asset_host_list [ i ] [ 2 ] ;
306+
307+ output += "<b>" + host + "</b>: " ;
308+ if ( activeCount > 0 ) {
309+ output += activeCount + " active<br/>" ;
310+ output += linksFromKeys ( switchPlannerInfo [ tab_id ] [ rewritten ] [ host ] [ 1 ] ) ;
311+ }
312+ if ( passiveCount > 0 ) {
313+ output += "<br/>" + passiveCount + " passive<br/>" ;
314+ output += linksFromKeys ( switchPlannerInfo [ tab_id ] [ rewritten ] [ host ] [ 0 ] ) ;
315+ }
316+ output += "<br/>" ;
317+ }
318+ return output ;
319+ }
320+
152321function onCookieChanged ( changeInfo ) {
153322 if ( ! changeInfo . removed && ! changeInfo . cookie . secure ) {
154323 if ( all_rules . shouldSecureCookie ( changeInfo . cookie , false ) ) {
@@ -250,3 +419,36 @@ chrome.tabs.onReplaced.addListener(function(addedTabId, removedTabId) {
250419// Listen for cookies set/updated and secure them if applicable. This function is async/nonblocking,
251420// so we also use onBeforeSendHeaders to prevent a small window where cookies could be stolen.
252421chrome . cookies . onChanged . addListener ( onCookieChanged ) ;
422+
423+ function disableSwitchPlannerFor ( tabId ) {
424+ delete switchPlannerEnabledFor [ tabId ] ;
425+ // Clear stored URL info.
426+ delete switchPlannerInfo [ tabId ] ;
427+ }
428+
429+ function enableSwitchPlannerFor ( tabId ) {
430+ switchPlannerEnabledFor [ tabId ] = true ;
431+ }
432+
433+ // Listen for connection from the DevTools panel so we can set up communication.
434+ chrome . runtime . onConnect . addListener ( function ( port ) {
435+ if ( port . name == "devtools-page" ) {
436+ chrome . runtime . onMessage . addListener ( function ( message , sender , sendResponse ) {
437+ var tabId = message . tabId ;
438+
439+ var disableOnCloseCallback = function ( port ) {
440+ log ( DBUG , "Devtools window for tab " + tabId + " closed, clearing data." ) ;
441+ disableSwitchPlannerFor ( tabId ) ;
442+ } ;
443+
444+ if ( message . type === "enable" ) {
445+ enableSwitchPlannerFor ( tabId ) ;
446+ port . onDisconnect . addListener ( disableOnCloseCallback ) ;
447+ } else if ( message . type === "disable" ) {
448+ disableSwitchPlannerFor ( tabId ) ;
449+ } else if ( message . type === "getSmallHtml" ) {
450+ sendResponse ( { html : switchPlannerSmallHtml ( tabId ) } ) ;
451+ }
452+ } ) ;
453+ }
454+ } ) ;
0 commit comments