| 
      1
     | 
    
      /**
 
     | 
  
  
    | 
      2
     | 
    
       * Hachette main content script run in all frames
 
     | 
  
  
    | 
      3
     | 
    
       *
 
     | 
  
  
    | 
      4
     | 
    
       * Copyright (C) 2021 Wojtek Kosior
 
     | 
  
  
    | 
      5
     | 
    
       * Copyright (C) 2021 jahoti
 
     | 
  
  
    | 
      6
     | 
    
       * Redistribution terms are gathered in the `copyright' file.
 
     | 
  
  
    | 
      7
     | 
    
       */
 
     | 
  
  
    | 
      8
     | 
    
      
 
     | 
  
  
    | 
      9
     | 
    
      /*
 
     | 
  
  
    | 
      10
     | 
    
       * IMPORTS_START
 
     | 
  
  
    | 
      11
     | 
    
       * IMPORT handle_page_actions
 
     | 
  
  
    | 
      12
     | 
    
       * IMPORT extract_signed
 
     | 
  
  
    | 
      13
     | 
    
       * IMPORT sign_data
 
     | 
  
  
    | 
      14
     | 
    
       * IMPORT gen_nonce
 
     | 
  
  
    | 
      15
     | 
    
       * IMPORT is_privileged_url
 
     | 
  
  
    | 
      16
     | 
    
       * IMPORT mozilla_suppress_scripts
 
     | 
  
  
    | 
      17
     | 
    
       * IMPORT is_chrome
 
     | 
  
  
    | 
      18
     | 
    
       * IMPORT is_mozilla
 
     | 
  
  
    | 
      19
     | 
    
       * IMPORT start_activity_info_server
 
     | 
  
  
    | 
      20
     | 
    
       * IMPORT modify_on_the_fly
 
     | 
  
  
    | 
      21
     | 
    
       * IMPORTS_END
 
     | 
  
  
    | 
      22
     | 
    
       */
 
     | 
  
  
    | 
      23
     | 
    
      
 
     | 
  
  
    | 
      24
     | 
    
      function accept_node(node, parent)
 
     | 
  
  
    | 
      25
     | 
    
      {
     | 
  
  
    | 
      26
     | 
    
          const clone = document.importNode(node, false);
 
     | 
  
  
    | 
      27
     | 
    
          node.hachette_corresponding = clone;
 
     | 
  
  
    | 
      28
     | 
    
          /*
 
     | 
  
  
    | 
      29
     | 
    
           * TODO: Stop page's own issues like "Error parsing a meta element's
 
     | 
  
  
    | 
      30
     | 
    
           * content:" from appearing as extension's errors.
 
     | 
  
  
    | 
      31
     | 
    
           */
 
     | 
  
  
    | 
      32
     | 
    
          parent.hachette_corresponding.appendChild(clone);
 
     | 
  
  
    | 
      33
     | 
    
      }
 
     | 
  
  
    | 
      34
     | 
    
      
 
     | 
  
  
    | 
      35
     | 
    
      function extract_cookie_policy(cookie, min_time)
 
     | 
  
  
    | 
      36
     | 
    
      {
     | 
  
  
    | 
      37
     | 
    
          let best_result = {time: -1};
     | 
  
  
    | 
      38
     | 
    
          let policy = null;
 
     | 
  
  
    | 
      39
     | 
    
          const extracted_signatures = [];
 
     | 
  
  
    | 
      40
     | 
    
      
 
     | 
  
  
    | 
      41
     | 
    
          for (const match of cookie.matchAll(/hachette-(\w*)=([^;]*)/g)) {
     | 
  
  
    | 
      42
     | 
    
      	const new_result = extract_signed(...match.slice(1, 3));
 
     | 
  
  
    | 
      43
     | 
    
      	if (new_result.fail)
 
     | 
  
  
    | 
      44
     | 
    
      	    continue;
 
     | 
  
  
    | 
      45
     | 
    
      
 
     | 
  
  
    | 
      46
     | 
    
      	extracted_signatures.push(match[1]);
 
     | 
  
  
    | 
      47
     | 
    
      
 
     | 
  
  
    | 
      48
     | 
    
      	if (new_result.time < Math.max(min_time, best_result.time))
 
     | 
  
  
    | 
      49
     | 
    
      	    continue;
 
     | 
  
  
    | 
      50
     | 
    
      
 
     | 
  
  
    | 
      51
     | 
    
      	/* This should succeed - it's our self-produced valid JSON. */
 
     | 
  
  
    | 
      52
     | 
    
      	const new_policy = JSON.parse(decodeURIComponent(new_result.data));
 
     | 
  
  
    | 
      53
     | 
    
      	if (new_policy.url !== document.URL)
 
     | 
  
  
    | 
      54
     | 
    
      	    continue;
 
     | 
  
  
    | 
      55
     | 
    
      
 
     | 
  
  
    | 
      56
     | 
    
      	best_result = new_result;
 
     | 
  
  
    | 
      57
     | 
    
      	policy = new_policy;
 
     | 
  
  
    | 
      58
     | 
    
          }
 
     | 
  
  
    | 
      59
     | 
    
      
 
     | 
  
  
    | 
      60
     | 
    
          return [policy, extracted_signatures];
 
     | 
  
  
    | 
      61
     | 
    
      }
 
     | 
  
  
    | 
      62
     | 
    
      
 
     | 
  
  
    | 
      63
     | 
    
      function extract_url_policy(url, min_time)
 
     | 
  
  
    | 
      64
     | 
    
      {
     | 
  
  
    | 
      65
     | 
    
          const [base_url, payload, anchor] =
 
     | 
  
  
    | 
      66
     | 
    
      	  /^([^#]*)#?([^#]*)(#?.*)$/.exec(url).splice(1, 4);
 
     | 
  
  
    | 
      67
     | 
    
      
 
     | 
  
  
    | 
      68
     | 
    
          const match = /^hachette_([^_]+)_(.*)$/.exec(payload);
 
     | 
  
  
    | 
      69
     | 
    
          if (!match)
 
     | 
  
  
    | 
      70
     | 
    
      	return [null, url];
 
     | 
  
  
    | 
      71
     | 
    
      
 
     | 
  
  
    | 
      72
     | 
    
          const result = extract_signed(...match.slice(1, 3));
 
     | 
  
  
    | 
      73
     | 
    
          if (result.fail)
 
     | 
  
  
    | 
      74
     | 
    
      	return [null, url];
 
     | 
  
  
    | 
      75
     | 
    
      
 
     | 
  
  
    | 
      76
     | 
    
          const original_url = base_url + anchor;
 
     | 
  
  
    | 
      77
     | 
    
          const policy = result.time < min_time ? null :
 
     | 
  
  
    | 
      78
     | 
    
      	  JSON.parse(decodeURIComponent(result.data));
 
     | 
  
  
    | 
      79
     | 
    
      
 
     | 
  
  
    | 
      80
     | 
    
          return [policy.url === original_url ? policy : null, original_url];
 
     | 
  
  
    | 
      81
     | 
    
      }
 
     | 
  
  
    | 
      82
     | 
    
      
 
     | 
  
  
    | 
      83
     | 
    
      function employ_nonhttp_policy(policy)
 
     | 
  
  
    | 
      84
     | 
    
      {
     | 
  
  
    | 
      85
     | 
    
          if (!policy.allow)
 
     | 
  
  
    | 
      86
     | 
    
      	return;
 
     | 
  
  
    | 
      87
     | 
    
      
 
     | 
  
  
    | 
      88
     | 
    
          policy.nonce = gen_nonce();
 
     | 
  
  
    | 
      89
     | 
    
          const [base_url, target] = /^([^#]*)(#?.*)$/.exec(policy.url).slice(1, 3);
 
     | 
  
  
    | 
      90
     | 
    
          const encoded_policy = encodeURIComponent(JSON.stringify(policy));
 
     | 
  
  
    | 
      91
     | 
    
          const payload = "hachette_" +
 
     | 
  
  
    | 
      92
     | 
    
      	  sign_data(encoded_policy, new Date().getTime()).join("_");
     | 
  
  
    | 
      93
     | 
    
          const resulting_url = `${base_url}#${payload}${target}`;
     | 
  
  
    | 
      94
     | 
    
          location.href = resulting_url;
 
     | 
  
  
    | 
      95
     | 
    
          location.reload();
 
     | 
  
  
    | 
      96
     | 
    
      }
 
     | 
  
  
    | 
      97
     | 
    
      
 
     | 
  
  
    | 
      98
     | 
    
      if (!is_privileged_url(document.URL)) {
     | 
  
  
    | 
      99
     | 
    
          let policy_received_callback = () => undefined;
 
     | 
  
  
    | 
      100
     | 
    
          let policy;
 
     | 
  
  
    | 
      101
     | 
    
      
 
     | 
  
  
    | 
      102
     | 
    
          /* Signature valid for half an hour. */
 
     | 
  
  
    | 
      103
     | 
    
          const min_time = new Date().getTime() - 1800 * 1000;
 
     | 
  
  
    | 
      104
     | 
    
      
 
     | 
  
  
    | 
      105
     | 
    
          if (/^https?:/.test(document.URL)) {
     | 
  
  
    | 
      106
     | 
    
      	let signatures;
 
     | 
  
  
    | 
      107
     | 
    
      	[policy, signatures] = extract_cookie_policy(document.cookie, min_time);
 
     | 
  
  
    | 
      108
     | 
    
      	for (const signature of signatures)
 
     | 
  
  
    | 
      109
     | 
    
      	    document.cookie = `hachette-${signature}=; Max-Age=-1;`;
     | 
  
  
    | 
      110
     | 
    
          } else {
     | 
  
  
    | 
      111
     | 
    
      	const scheme = /^([^:]*)/.exec(document.URL)[1];
 
     | 
  
  
    | 
      112
     | 
    
      	const known_scheme = ["file", "ftp"].includes(scheme);
 
     | 
  
  
    | 
      113
     | 
    
      
 
     | 
  
  
    | 
      114
     | 
    
      	if (!known_scheme)
 
     | 
  
  
    | 
      115
     | 
    
      	    console.warn(`Unknown url scheme: \`${scheme}'!`);
     | 
  
  
    | 
      116
     | 
    
      
 
     | 
  
  
    | 
      117
     | 
    
      	let original_url;
 
     | 
  
  
    | 
      118
     | 
    
      	[policy, original_url] = extract_url_policy(document.URL, min_time);
 
     | 
  
  
    | 
      119
     | 
    
      	history.replaceState(null, "", original_url);
 
     | 
  
  
    | 
      120
     | 
    
      
 
     | 
  
  
    | 
      121
     | 
    
      	if (known_scheme && !policy)
 
     | 
  
  
    | 
      122
     | 
    
      	    policy_received_callback = employ_nonhttp_policy;
 
     | 
  
  
    | 
      123
     | 
    
          }
 
     | 
  
  
    | 
      124
     | 
    
      
 
     | 
  
  
    | 
      125
     | 
    
          if (!policy) {
     | 
  
  
    | 
      126
     | 
    
      	console.warn("Using default policy!");
     | 
  
  
    | 
      127
     | 
    
      	policy = {allow: false, nonce: gen_nonce()};
     | 
  
  
    | 
      128
     | 
    
          }
 
     | 
  
  
    | 
      129
     | 
    
      
 
     | 
  
  
    | 
      130
     | 
    
          handle_page_actions(policy.nonce, policy_received_callback);
 
     | 
  
  
    | 
      131
     | 
    
      
 
     | 
  
  
    | 
      132
     | 
    
          if (!policy.allow) {
     | 
  
  
    | 
      133
     | 
    
      	if (is_mozilla) {
     | 
  
  
    | 
      134
     | 
    
      	    const script = document.querySelector("script");
     | 
  
  
    | 
      135
     | 
    
      	    if (script)
 
     | 
  
  
    | 
      136
     | 
    
      		script.textContent = "throw 'blocked';\n" + script.textContent;
 
     | 
  
  
    | 
      137
     | 
    
      	}
 
     | 
  
  
    | 
      138
     | 
    
      	const old_html = document.documentElement;
 
     | 
  
  
    | 
      139
     | 
    
      	const new_html = document.createElement("html");
     | 
  
  
    | 
      140
     | 
    
      	old_html.replaceWith(new_html);
 
     | 
  
  
    | 
      141
     | 
    
      	old_html.hachette_corresponding = new_html;
 
     | 
  
  
    | 
      142
     | 
    
      
 
     | 
  
  
    | 
      143
     | 
    
      	const modify_end =
 
     | 
  
  
    | 
      144
     | 
    
      	      modify_on_the_fly(old_html, policy, {node_eater: accept_node});
     | 
  
  
    | 
      145
     | 
    
      	document.addEventListener("DOMContentLoaded", modify_end);
     | 
  
  
    | 
      146
     | 
    
          }
 
     | 
  
  
    | 
      147
     | 
    
      
 
     | 
  
  
    | 
      148
     | 
    
          start_activity_info_server();
 
     | 
  
  
    | 
      149
     | 
    
      }
 
     |