PageSourceSearch

https://cdn4.hola.org/svc/vpn/pub/util.js

js hola.org collected 2026-10-01 13:02:20 UTC 13,535 bytes, 430 lines download raw bytes

1// LICENSE_CODE ZON
2'use strict'; /*jslint browser:true, node:true, es6:true*/
3(function(){
4let define;
5const is_node = typeof module=='object' && module.exports && module.children;
6if (!is_node)
7    define = self.define;
8else
9    define = require('../../../util/require_node.js').define(module, '../');
10define(['/util/sprintf.js', '/util/util.js', '/util/url.js',
11    '/util/version_util.js'], function(sprintf, zutil, zurl, version_util)
12{
13const E = {};
14const assign = Object.assign;
15
16E.get_root_domain = zurl.get_root_domain;
17
18E.get_root_url = url=>{
19    // XXX sergey: quick hack for trial in hola browser
20    if (url=='chrome://newtab/')
21        return 'newtab';
22    const n = (url||'').match(/^https?:\/\/([^\/]+)(\/.*)?$/);
23    if (!n || !n[1])
24        return null;
25    return E.get_root_domain(n[1]);
26};
27
28E.find_rule = (rules, opt)=>{
29    if (!rules)
30        return;
31    for (let i in rules)
32    {
33        const r = rules[i];
34        if (opt.name==r.name
35            && (opt.type===undefined || opt.type==r.type)
36            && (opt.md5===undefined || opt.md5==r.md5)
37            && (opt.country===undefined
38            || opt.country.toLowerCase()==r.country.toLowerCase()))
39        {
40            return r;
41        }
42    }
43    return null;
44};
45
46E.gen_route_str = (route_opt, opt)=>{
47    opt = opt||{};
48    if (route_opt.direct)
49        return 'direct';
50    let s = route_opt.country.toLowerCase(), r = [];
51    if (route_opt.peer)
52        r.push('peer');
53    if (route_opt.pool)
54    {
55        r.push('pool'+(typeof route_opt.pool=='string' ?
56            '_'+route_opt.pool : ''));
57    }
58    if (!opt.no_algo && route_opt.algo)
59        r.push(route_opt.algo);
60    if (r.length)
61        s += '.'+r.join(',');
62    return s;
63};
64
65function ip_list_to_agents(ip_list){
66    return fix_zagent(Object.keys(ip_list).join(','));
67}
68function fix_zagent(s){
69    return s.replace(/.hola.org/g, '').replace(/zagent/g, 'za');
70}
71
72// XXX arik: mv to util?
73function pad(num, size){ return ('000'+num).slice(-size); }
74function time_str(d){
75    d = new Date(d);
76    if (isNaN(d))
77        return '00:00:00.000';
78    return pad(d.getUTCHours(), 2)+':'+pad(d.getUTCMinutes(), 2)
79    +':'+pad(d.getUTCSeconds(), 2)
80    +'.'+pad(d.getUTCMilliseconds(), 3);
81}
82
83E.events_enabled = false;
84E.events = [];
85E.events_n = {};
86E.reset_events = ()=>{
87    E.events = [];
88    E.events_n = {};
89};
90E.push_event = (name, opt)=>{
91    try {
92        if (!E.events_enabled)
93            return;
94        if (E.events.length > 512)
95            E.events.splice(0, E.events.length/2);
96        E.events.push({name, ts: Date.now(), opt});
97        E.events_n[name] = (E.events_n[name]||0)+1;
98    } catch(err){ console.error('push_event error %s', err&&err.stack); }
99};
100E.enable_events_logging = enabled=>{
101    E.events_enabled = !!enabled;
102    if (!enabled)
103        E.reset_events();
104};
105E.get_event_n = e=>(E.events_n[e]);
106E.events_to_str = ()=>{
107    let s = '', a =[];
108    E.events.forEach(e=>{
109        let s = time_str(e.ts)+' '+e.name, o = e.opt;
110        switch (e.name)
111        {
112        case '>zgettunnels':
113            s += sprintf(' %s%s', o.country,
114                (o.exclude||[]).length ? ' exclude '+
115                o.exclude.map(e=>fix_zagent(e)).join(',') : '');
116            break;
117        case '<zgettunnels':
118            s += sprintf(' %s%s', o.country,
119                ' agents '+ip_list_to_agents(o.ip_list));
120            break;
121        case 'set_rule':
122            s += sprintf(' %s%s%s%s%s', o.name,
123                o.enabled ? ' '+o.country : ' off',
124                o.del ? ' del' : '',
125                o.src ? ' src '+o.src : '',
126                !o.full_vpn ? ' !full_vpn' : '',
127                o.mode=='unblock' ? '' : ' mode '+o.mode);
128            break;
129        case '<verify_err':
130            s += sprintf(' %s %s', o.host ? fix_zagent(o.host) : o.ip,
131                ' '+o.err);
132            break;
133        case '<verify':
134            s += sprintf(' %s %s', o.host ? fix_zagent(o.host) : o.ip,
135                o.rtt ?o.rtt+'ms' : '!rtt');
136            break;
137        case '>verify':
138            s += sprintf(' %s', o.host ? fix_zagent(o.host) : o.ip);
139            break;
140        case '>stuck':
141        case '>stuck_long':
142           break;
143        case '<stuck_end':
144            s += sprintf(' %sms', o.duration);
145           break;
146        case 'pac_miss':
147            s += sprintf(' %s!=%s %s', o.req_ip, o.agent_ip, o.url);
148            break;
149        case 'pac_reload':
150            s += sprintf(' after %ssec', (o.new_ts - o.init_ts)/1000);
151            break;
152        case 'navgiate': s += sprintf(' tab %s %s', o.tab_id, o.url); break;
153        case 'popup_render':
154        case 'tpopup_render':
155            break;
156        case 'pac_slow':
157            s += sprintf(' %dms %s', o.dur, (o.url||'').substr(0, 50));
158            break;
159        default: s += JSON.stringify(o)||''; break;
160        }
161        a.push(s);
162    });
163    a = a.reverse();
164    return a.join('\n');
165};
166
167class Domain_tree {
168    constructor(){
169        this.clear();
170    }
171    *[Symbol.iterator](){
172        let parts = [];
173        for (let item of Domain_tree.iterate_subtree(parts, this.tree))
174            yield item;
175    }
176    clear(){
177        this.tree = {};
178    }
179    get_value(domain){
180        if (!domain)
181            return;
182        let parts = domain.split('.');
183        let leaf = this.tree, value = leaf[Domain_tree.VALUE];
184        for (let i = parts.length-1; leaf && i>=0; i--)
185        {
186            leaf = leaf[parts[i]];
187            value = leaf && (Domain_tree.VALUE in leaf) ?
188                leaf[Domain_tree.VALUE] : value;
189        }
190        return value;
191    }
192    set_value(domain, value){
193        if (!domain)
194            return void (this.tree[Domain_tree.VALUE] = value);
195        let parts = domain.split('.');
196        let leaf = this.tree;
197        for (let i = parts.length-1; i>=0; i--)
198            leaf = leaf[parts[i]] = leaf[parts[i]]||{};
199        leaf[Domain_tree.VALUE] = value;
200    }
201}
202Domain_tree.VALUE = Symbol.for('value');
203Domain_tree.iterate_subtree = function*(parts, leaf){
204    if (Domain_tree.VALUE in leaf)
205        yield [parts.slice().reverse().join('.'), leaf[Domain_tree.VALUE]];
206    for (let key in leaf)
207    {
208        parts.push(key);
209        for (let item of Domain_tree.iterate_subtree(parts, leaf[key]))
210            yield item;
211        parts.pop();
212    }
213};
214
215E.Domain_tree = Domain_tree;
216
217E.get_site_key = (conf, ext_opt, root_url)=>{
218    let {ext_ver} = ext_opt;
219    let sites = conf && conf.sites || {};
220    return Object.keys(sites).find(k=>{
221        let v = sites[k];
222        let urls = Array.isArray(v.root_url) ? v.root_url : [v.root_url];
223        return urls.includes(root_url) && E.check_min_ver(ext_ver, v.min_ver);
224    });
225};
226
227const get_match_fn = operator=>{
228    if (operator=='gt')
229        return (a, b)=>a>b;
230    if (operator=='gte')
231        return (a, b)=>a>=b;
232    if (operator=='lt')
233        return (a, b)=>a<b;
234    if (operator=='lte')
235        return (a, b)=>a<=b;
236    if (operator=='re')
237        return (a, b)=>new RegExp(b).test(a);
238    if (operator=='in' || operator=='nin')
239    {
240        return (a, b)=>{
241            let found;
242            if (!Array.isArray(b))
243                return false; // bad conf
244            if (!Array.isArray(a))
245                found = b.includes(a);
246            else
247                found = b.some(i=>a.includes(i));
248            return operator=='in' ? found : !found;
249        };
250    }
251    if (operator=='eq' || operator=='neq')
252    {
253        return (a, b)=>{
254            let equal;
255            if (!Array.isArray(a))
256                equal = a==b;
257            else if (!Array.isArray(b))
258                equal = a.includes(b);
259            else
260                equal = b.every(i=>a.includes(i));
261            return operator=='eq' ? equal : !equal;
262        };
263    }
264    return (a, b)=>false;
265};
266
267E.match_filter = (query, filter)=>{
268    if (!filter)
269        return true;
270    if (!query)
271        return false;
272    query = assign({}, query);
273    for (let key in query)
274    {
275        if (key.endsWith('_ts') && query.now)
276            query['since_'+key] = query.now-query[key];
277    }
278    for (let f_key in filter)
279    {
280        if (!f_key)
281            continue;
282        let m = f_key.match(/^(.*)_(gt|gte|lt|lte|in|nin|re|eq|neq)$/);
283        let q_key = m ? m[1] : f_key;
284        let operator = m ? m[2] : 'eq';
285        // fix for filters like {signed_in: true}
286        if (operator=='in' && !Array.isArray(filter[f_key]) ||
287            operator=='nin' && !Array.isArray(filter[f_key]))
288        {
289            q_key = f_key;
290            operator = 'eq';
291        }
292        let op1, op2;
293        if (q_key.endsWith('_ver') && /(gt|gte|lt|lte|eq|neq)/.test(operator))
294        {
295            op1 = version_util.cmp(query[q_key], filter[f_key]);
296            op2 = 0;
297        }
298        else
299        {
300            op1 = query[q_key];
301            op2 = filter[f_key];
302        }
303        if (!get_match_fn(operator)(op1, op2))
304            return false;
305    }
306    return true;
307};
308
309const apply_conf_override = (site_conf, ext_opt)=>{
310    let {ext_ver, install_ver, install_ts} = ext_opt;
311    let {override} = site_conf;
312    site_conf = zutil.omit(site_conf, ['override']);
313    if (!override)
314        return site_conf;
315    let versions = Object.keys(override).sort((a, b)=>
316        -version_util.cmp(a, b));
317    let ver = versions.find(v=>E.check_min_ver(ext_ver, v) &&
318        E.check_min_install_ver(install_ver, override[v].install_min_ver) &&
319        E.check_min_install_ts(install_ts, override[v].install_min_ts)
320    );
321    if (ver)
322    {
323        site_conf = zutil.extend_deep(zutil.clone_deep(site_conf),
324            zutil.omit(override[ver], ['install_min_ver', 'install_min_ts']));
325    }
326    return site_conf;
327};
328
329const apply_trial_conf_override = (tconf, ext_opt)=>{
330    let {override} = tconf;
331    tconf = zutil.omit(tconf, ['override']);
332    if (!override || !ext_opt)
333        return tconf;
334    let {ext_ver, trial_state, now} = ext_opt;
335    let first_start_ts = trial_state && trial_state.first_start_ts;
336    // old ext verssions do not support override
337    if (!first_start_ts || !now || version_util.cmp(ext_ver, '1.174.712')<0)
338        return tconf;
339    let match_opt = assign({first_start_ts}, ext_opt);
340    delete match_opt.trial_state;
341    // XXX sergey: for backward compat. New versions should use since_* syntax
342    match_opt.first_start = now - first_start_ts;
343    // find last match
344    let ov = override.slice().reverse()
345        .find(o=>E.match_filter(match_opt, o.filter));
346    if (ov)
347        tconf = zutil.extend_deep(zutil.clone_deep(tconf), ov.conf);
348    return tconf;
349};
350
351E.get_all_sites_conf = (conf, ext_opt)=>{
352    let {ext_ver, install_ver, install_ts} = ext_opt;
353    let c = conf && conf.sites_default || {};
354    if (!E.check_min_ver(ext_ver, c.min_ver) ||
355        !E.check_min_install_ver(install_ver, c.install_min_ver) ||
356        !E.check_min_install_ts(install_ts, c.install_min_ts))
357    {
358        return;
359    }
360    return apply_conf_override(c, ext_opt);
361};
362
363E.get_specific_site_conf = (conf, ext_opt, root_url)=>{
364    let site_conf = zutil.get(conf && conf.sites, E.get_site_key(conf, ext_opt,
365        root_url));
366    if (site_conf)
367        site_conf = apply_conf_override(site_conf, ext_opt);
368    return site_conf;
369};
370
371E.get_site_conf = (conf, ext_opt, root_url, path, def)=>{
372    let site_conf = root_url &&
373        E.get_specific_site_conf(conf, ext_opt, root_url) ||
374        E.get_all_sites_conf(conf, ext_opt);
375    return path ? zutil.get(site_conf, path, def) : site_conf;
376};
377
378E.get_trial_conf = (conf, ext_opt, root_url, path, def)=>{
379    let {ext_ver} = ext_opt;
380    let tconf = E.get_site_conf(conf, ext_opt, root_url, 'trial');
381    if (!tconf)
382        return;
383    let def_trial = zutil.get(E.get_all_sites_conf(conf, ext_opt), 'trial');
384    if (tconf.all_sites_timer && !def_trial)
385        tconf = zutil.omit(tconf, ['all_sites_timer']);
386    // if all_sites_timer is enabled, we must use common trial conf for all
387    // sites, but each site can have own skip_signin value
388    if (tconf.all_sites_timer && def_trial)
389        tconf = assign({}, tconf, zutil.omit(def_trial, ['skip_signin']));
390    tconf = apply_trial_conf_override(tconf, ext_opt);
391    let {time_ended_bonus} = tconf;
392    if (time_ended_bonus && !E.check_min_ver(ext_ver,
393        time_ended_bonus.min_ver))
394    {
395        tconf = zutil.omit(tconf, ['time_ended_bonus']);
396    }
397    return path ? zutil.get(tconf, path, def) : tconf;
398};
399
400E.check_min_ver = (ext_ver, min_ver)=>
401    version_util.valid(min_ver) && version_util.cmp(ext_ver, min_ver)>=0;
402
403E.check_min_install_ver = (install_ver, min_ver)=>!min_ver ||
404    version_util.valid(min_ver) && version_util.cmp(install_ver, min_ver)>=0;
405
406E.check_min_install_ts = (install_ts, ts)=>!ts || +install_ts>=+ts;
407
408// browser-side twin of svc/lib/rmt_update.js:get_product/pick_version
409// (that one is node-only); see also svc/vpn/peer/util.js:parse_appid
410E.get_product = appid=>{
411    if (!appid)
412        return undefined;
413    if (appid.startsWith('win_hola.browser_and_vpn.'))
414        return 'browser+vpn';
415    if (appid.startsWith('win_hola.browser.'))
416        return 'browser';
417    return 'vpn';
418};
419
420E.pick_version = (versions, product, suffix)=>{
421    if (!product)
422        return versions[suffix];
423    const keys = product.split('+');
424    if (keys.length==1)
425        return versions[`${keys[0]}_${suffix}`];
426    return keys.map(k=>versions[`${k}_${suffix}`])
427        .reduce((min, v)=>version_util.cmp(v, min)<0 ? v : min);
428};
429
430return E; }); }());

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.