summaryrefslogtreecommitdiffstats
path: root/src/scripts/matcher.js
blob: 19cd49d44a4c0ac019bda244bc8108d90a00446a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
'use strict';

const schemeSet = {
  all : 1,
  http: 2,
  https: 4
};
// Shortcuts so we dont perform i18n lookups for every non-match
const FOR_ALL = {originalPattern: chrome.i18n.getMessage('forAll')}
const NOMATCH_TEXT = chrome.i18n.getMessage('noMatch');
const NONE_TEXT = chrome.i18n.getMessage('none');
const NOMATCH_COLOR = '#D3D3D3';
const WHITE = chrome.i18n.getMessage('white');
const BLACK = chrome.i18n.getMessage('black');

function findProxyMatch(url, activeSettings) {
  // note: we've already thrown out inactive settings and inactive patterns in background.js.
  // we're not iterating over them
  
  if (activeSettings.mode === 'patterns') {
    // Unfortunately, since Firefox 57 and some releases afterwards, we were unable
    // to get anything of the URL except scheme, port, and host (because of Fx's PAC
    // implementation). Now we have access to rest of URL, like pre-57, but users
    // have written their patterns not anticipating that. Need to do more research
    // before using other parts of URL. For now, we ignore the other parts.
    const parsedUrl = new URL(url);
    const scheme = parsedUrl.protocol.substring(0, parsedUrl.protocol.length-1); // strip the colon
    const hostPort = parsedUrl.host; // This includes port if one is specified

    for (const proxy of activeSettings.proxySettings) {
      
      // Check black patterns first
      const blackMatch = proxy.blackPatterns.find(item => 
        (item.protocols === schemeSet.all || item.protocols === schemeSet[scheme]) &&
          item.pattern.test(hostPort));

      if (blackMatch) {
        sendToMatchedLog(url, proxy, Utils.getProxyTitle(proxy), blackMatch, BLACK);
        continue; // if blacklist matched, continue to the next proxy
      }

      const whiteMatch = proxy.whitePatterns.find(item =>
        (item.protocols === schemeSet.all || item.protocols === schemeSet[scheme]) &&
          item.pattern.test(hostPort));
      
      if (whiteMatch) {
  			// found a whitelist match, end here
        const title = Utils.getProxyTitle(proxy);
        Utils.updateIcon('images/icon.svg', proxy.color, title, false, title, false);
        sendToMatchedLog(url, proxy, title, whiteMatch, WHITE);
        return prepareSetting(proxy);
  		}
    }
    // no white matches in any settings
    sendToUnmatchedLog(url);
    Utils.updateIcon('images/gray.svg', null, NOMATCH_TEXT, false, NOMATCH_TEXT, false);
    return {type: 'direct'};
  }
  else if (activeSettings.mode === 'disabled') {
    // Generally we won't get to this block because our proxy handler is turned off in this mode.
    // We will get here at startup and also if there is a race condition between removing our listener
    // (when switching to disabled mode) and handaling requests.
    return {type: 'direct'};    
  }
  else {
    // Fixed mode -- use 1 proxy for all URLs
    const p = activeSettings.proxySettings[0];
    const title = Utils.getProxyTitle(p);
    Utils.updateIcon('images/icon.svg', p.color, title, false, title, false);
    sendToMatchedLog(url, p, title, FOR_ALL);
    return prepareSetting(p);
  }
}

const typeSet = {
  1: 'http',    // PROXY_TYPE_HTTP
  2: 'https',   // PROXY_TYPE_HTTPS
  3: 'socks',   // PROXY_TYPE_SOCKS5
  4: 'socks4',  // PROXY_TYPE_SOCKS4
  5: 'direct'   // PROXY_TYPE_NONE
};

function prepareSetting(proxy) {
  const ret = {
    type: typeSet[proxy.type] || typeSet[5], // If 'direct', all other properties of this object are ignored.
    host: proxy.address, 
    port: proxy.port
  };
  proxy.username && (ret.username = proxy.username);
  proxy.password && (ret.password = proxy.password);
  proxy.proxyDNS && (ret.proxyDNS = proxy.proxyDNS); // Only useful for SOCKS
  //if ((proxy.type === PROXY_TYPE_HTTP || proxy.type === PROXY_TYPE_HTTPS) && proxy.username && proxy.password) {
    // Using wireshark, I do not see this header being sent, contrary to
    // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/proxy/ProxyInfo
    //ret.proxyAuthorizationHeader = 'Basic ' + btoa(proxy.username + ":" + proxy.password);
  //}
  return ret;
}

function sendToMatchedLog(url, proxy, title, matchedPattern, whiteBlack) {
  // log only the data that is needed for display
  logger && logger.active && logger.addMatched({
    url,
    title,
    color: proxy.color,
    address: proxy.address,
    // Log should display whatever user typed, not our processed version of the pattern
    matchedPattern: matchedPattern.originalPattern,
    whiteBlack,
    timestamp: Date.now()
  });
}

function sendToUnmatchedLog(url) {
  logger && logger.active && logger.addUnmatched({url, timestamp: Date.now()});
}