/*******************************************************************************

    uBlock Origin - a browser extension to block requests.
    Copyright (C) 2014-present Raymond Hill

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see {http://www.gnu.org/licenses/}.

    Home: https://github.com/gorhill/uBlock
*/

'use strict';

/******************************************************************************/

import publicSuffixList from '../lib/publicsuffixlist/publicsuffixlist.js';
import punycode from '../lib/punycode.js';

/******************************************************************************/

// Originally:
// https://github.com/gorhill/uBlock/blob/8b5733a58d3acf9fb62815e14699c986bd1c2fdc/src/js/uritools.js

const reHostnameFromCommonURL =
    /^https:\/\/[0-9a-z._-]+[0-9a-z]\//;
const reAuthorityFromURI =
    /^(?:[^:\/?#]+:)?(\/\/[^\/?#]+)/;
const reHostFromNakedAuthority =
    /^[0-9a-z._-]+[0-9a-z]$/i;
const reHostFromAuthority =
    /^(?:[^@]*@)?([^:]+)(?::\d*)?$/;
const reIPv6FromAuthority =
    /^(?:[^@]*@)?(\[[0-9a-f:]+\])(?::\d*)?$/i;
const reMustNormalizeHostname =
    /[^0-9a-z._-]/;
const reOriginFromURI =
    /^[^:\/?#]+:\/\/[^\/?#]+/;
const reHostnameFromNetworkURL =
    /^(?:http|ws|ftp)s?:\/\/([0-9a-z_][0-9a-z._-]*[0-9a-z])(?::\d+)?\//;
const reIPAddressNaive =
    /^\d+\.\d+\.\d+\.\d+$|^\[[\da-zA-Z:]+\]$/;
const reNetworkURI =
    /^(?:ftps?|https?|wss?):\/\//;

// For performance purpose, as simple tests as possible
const reIPv4VeryCoarse = /\.\d+$/;
const reHostnameVeryCoarse = /[g-z_\-]/;

/******************************************************************************/

function domainFromHostname(hostname) {
    return reIPAddressNaive.test(hostname)
        ? hostname
        : publicSuffixList.getDomain(hostname);
}

function domainFromURI(uri) {
    if ( !uri ) { return ''; }
    return domainFromHostname(hostnameFromURI(uri));
}

function entityFromDomain(domain) {
    const pos = domain.indexOf('.');
    return pos !== -1 ? domain.slice(0, pos) + '.*' : '';
}

function hostnameFromURI(uri) {
    let match = reHostnameFromCommonURL.exec(uri);
    if ( match !== null ) { return match[0].slice(8, -1); }
    match = reAuthorityFromURI.exec(uri);
    if ( match === null ) { return ''; }
    const authority = match[1].slice(2);
    if ( reHostFromNakedAuthority.test(authority) ) {
        return authority.toLowerCase();
    }
    match = reHostFromAuthority.exec(authority);
    if ( match === null ) {
        match = reIPv6FromAuthority.exec(authority);
        if ( match === null ) { return ''; }
    }
    let hostname = match[1];
    while ( hostname.endsWith('.') ) {
        hostname = hostname.slice(0, -1);
    }
    if ( reMustNormalizeHostname.test(hostname) ) {
        hostname = punycode.toASCII(hostname.toLowerCase());
    }
    return hostname;
}

function hostnameFromNetworkURL(url) {
    const matches = reHostnameFromNetworkURL.exec(url);
    return matches !== null ? matches[1] : '';
}

function originFromURI(uri) {
    let match = reHostnameFromCommonURL.exec(uri);
    if ( match !== null ) { return match[0].slice(0, -1); }
    match = reOriginFromURI.exec(uri);
    return match !== null ? match[0].toLowerCase() : '';
}

function isNetworkURI(uri) {
    return reNetworkURI.test(uri);
}

/******************************************************************************/

function toBroaderHostname(hostname) {
    const pos = hostname.indexOf('.');
    if ( pos !== -1 ) {
        return hostname.slice(pos + 1);
    }
    return hostname !== '*' && hostname !== '' ? '*' : '';
}

function toBroaderIPv4Address(ipaddress) {
    if ( ipaddress === '*' || ipaddress === '' ) { return ''; }
    const pos = ipaddress.lastIndexOf('.');
    if ( pos === -1 ) { return '*'; }
    return ipaddress.slice(0, pos);
}

function toBroaderIPv6Address(ipaddress) {
    return ipaddress !== '*' && ipaddress !== '' ? '*' : '';
}

function decomposeHostname(hostname, out) {
    if ( out.length !== 0 && out[0] === hostname ) {
        return out;
    }
    let broadenFn;
    if ( reHostnameVeryCoarse.test(hostname) === false ) {
        if ( reIPv4VeryCoarse.test(hostname) ) {
            broadenFn = toBroaderIPv4Address;
        } else if ( hostname.startsWith('[') ) {
            broadenFn = toBroaderIPv6Address;
        }
    }
    if ( broadenFn === undefined ) {
        broadenFn = toBroaderHostname;
    }
    out[0] = hostname;
    let i = 1;
    for (;;) {
        hostname = broadenFn(hostname);
        if ( hostname === '' ) { break; }
        out[i++] = hostname;
    }
    out.length = i;
    return out;
}

/******************************************************************************/

export {
    decomposeHostname,
    domainFromHostname,
    domainFromURI,
    entityFromDomain,
    hostnameFromNetworkURL,
    hostnameFromURI,
    isNetworkURI,
    originFromURI,
};
