<?php
/**
 * url地址相关的操作类
 * Formatter: https://www.duplichecker.com/php-formatter
 *
 * @file addressMaker.class.php
 * @author gently
 * @date 2017.12.31
 *
 *
 */
!defined("ROOT_DIR") && die("Access Denied.");

class addressMaker
{
    const LINK_URL = "https://github.com/privacy-protection-tools/anti-AD";

    /**
     * 分离域名
     *
     * @param $str_domain
     * @return string
     */
    public static function extract_main_domain($str_domain)
    {
        if (empty($str_domain)) {
            return "";
        }

        $str_reg = "/^(?:(?:[a-z0-9\-]*[a-z0-9]\.)*?|\.)?([a-z0-9\-]*[a-z0-9](";
        /************start CN域名的特殊处理规则，其中包括了各行政区特别后缀的cn域名*****************************/
        $str_reg .=
            "\.ac\.cn|\.ah\.cn|\.bj\.cn|\.com\.cn|\.cq\.cn|\.fj\.cn|\.gd\.cn|\.gov\.cn|\.gs\.cn";
        $str_reg .=
            "|\.gx\.cn|\.gz\.cn|\.ha\.cn|\.hb\.cn|\.he\.cn|\.hi\.cn|\.hk\.cn|\.hl\.cn|\.hn\.cn";
        $str_reg .=
            "|\.jl\.cn|\.js\.cn|\.jx\.cn|\.ln\.cn|\.mo\.cn|\.net\.cn|\.nm\.cn|\.nx\.cn|\.org\.cn";
        $str_reg .=
            "|\.qh\.cn|\.sc\.cn|\.sd\.cn|\.sh\.cn|\.sn\.cn|\.sx\.cn|\.tj\.cn|\.tw\.cn|\.xj\.cn";
        $str_reg .= "|\.xz\.cn|\.yn\.cn|\.zj\.cn|\.edu.cn";
        /************end CN域名的特殊处理规则，其中包括了各行政区特别后缀的cn域名******************************/
        $str_reg .= "|\.cn|\.com|\.net|\.org|\.me|\.co|\.info|\.cc|\.tv";
        $str_reg .=
            "|\.pw|\.biz|\.top|\.win|\.bid|\.cf|\.club|\.ne|\.de|\.la|\.us|\.mobi|\.hn|\.asia";
        $str_reg .=
            "|\.jp|\.tw|\.am|\.hk|\.site|\.live|\.xyz|\.space|\.fr|\.es|\.nl|\.au|\.in|\.ru";
        $str_reg .=
            "|\.su|\.world|\.io|\.trade|\.bet|\.im|\.fm|\.today|\.wang|\.rocks|\.vip|\.eu|\.run";
        $str_reg .=
            "|\.online|\.website|\.cricket|\.date|\.men|\.ca|\.xxx|\.name|\.pl|\.be|\.il|\.gov|\.it";
        $str_reg .=
            "|\.cl|\.tk|\.cz|\.hu|\.ro|\.vg|\.ws|\.nu|\.vn|\.lt|\.edu|\.lv|\.mx|\.by|\.gr|\.br|\.fi";
        $str_reg .=
            "|\.pt|\.dk|\.se|\.at|\.id|\.ve|\.ir|\.ma|\.ch|\.nf|\.bg|\.ua|\.is|\.hr|\.shop|\.xin|\.si|\.or";
        $str_reg .=
            "|\.sk|\.kz|\.tt|\.so|\.gg|\.ms|\.ink|\.pro|\.work|\.click|\.link|\.ly|\.ai|\.tech|\.kr|\.to";
        $str_reg .=
            "|\.uk|\.ad|\.ac|\.md|\.ml|\.cm|\.re|\.ph|\.my|\.lu|\.network|\.sh|\.fun|\.az|\.cx|\.ga";
        $str_reg .=
            "|\.ae|\.bz|\.gq|\.gs|\.pk|\.sex|\.stream|\.support|\.pub|\.nz|\.ng|\.zw|\.sx|\.studio|\.media|\.zone";
        $str_reg .=
            "|\.icu|\.ie|\.li|\.bar|\.video|\.wiki|\.ltd|\.cash|\.pink|\.loan|\.gdn|\.app|\.ovh|\.land|\.st|\.how";
        $str_reg .=
            "|\.kim|\.download|\.ag|\.technology|\.company|\.guru|\.gt|\.sg|\.photo|\.digital|\.one|\.tr";
        $str_reg .=
            "|\.show|\.sncf|\.uz|\.as|\.ee|\.fyi|\.cloud|\.group|\.promo|\.party|\.services|\.life|\.no";
        $str_reg .=
            "|\.watch|\.works|\.buzz|\.best|\.center|\.host|\.style|\.press|\.solutions|\.exchange|\.wtf";
        $str_reg .=
            "|\.delivery|\.page|\.webcam|\.cam|\.supply|\.accountant|\.systems|\.agency|\.science|\.awe";
        $str_reg .=
            "|\.gd|\.review|\.tc|\.mn|\.cool|\.monster|\.do|\.bi|\.news|\.boom|\.lol|\.events|\.jobs";
        $str_reg .=
            "|\.ooo|\.social|\.ninja|\.blue|\.plus|\.racing|\.ht|\.tl|\.cat|\.tf|\.al|\.vc|\.cr";
        $str_reg .= ")";

        $str_reg .=
            "(\.hk|\.tw|\.uk|\.jp|\.kr|\.th|\.au|\.ua|\.so|\.br|\.sg|\.pt|\.ec|\.ar|\.my";
        $str_reg .=
            "|\.tr|\.bd|\.mk|\.za|\.mt|\.sm|\.ge|\.kg|\.ke|\.de|\.ve|\.es|\.ru|\.pk|\.mx";
        $str_reg .= "|\.nz|\.py|\.pe|\.ph|\.pl|\.ng|\.pa|\.fj";

        $str_reg .= ')?)$/';
        if (preg_match($str_reg, $str_domain, $matches)) {
            return strval($matches[1]);
        }

        return "";
    }

    /**
     * 从 easylist类源文件中提取可用地址
     *
     * @param String $str_easylist 原始的easylist列表字符串
     * @param Boolean $strict_mode 严格模式，启用时将屏蔽该域所在的主域名，例如www.baidu.com，将获取到baidu.com并写入最终列表
     * @param Array $arr_whitelist 白名单列表
     * @return array
     */
    public static function get_domain_from_easylist(
        $str_easylist,
        $strict_mode = false,
        $arr_whitelist = []
    ) {
        $strlen = strlen($str_easylist);
        if ($strlen < 10) {
            return [];
        }

        $str_easylist = $str_easylist . "\n"; //防止最后一行没有换行符

        $i = 0;
        $arr_domains = [];
        while ($i < $strlen) {
            $end_pos = strpos($str_easylist, "\n", $i);
            $line = trim(substr($str_easylist, $i, $end_pos - $i));
            $i = $end_pos + 1;
            if (empty($line) || strlen($line) < 3) {
                continue;
            }

            if ($line[0] != "|" || $line[1] != "|") {
                continue;
            }

            if (
                preg_match(
                    '/^\|\|([0-9a-z\-\.]+[a-z]+)\^(\$([^=~]+?,)?(image|third-party|script|subdocument|popup|rewrite)(,[^=~]+)?)?$/',
                    $line,
                    $matches
                )
            ) {
                if (substr($matches[1], 0, 4) == "www.") {
                    $row = substr($matches[1], 4);
                } else {
                    $row = $matches[1];
                }
                $main_domain = self::extract_main_domain($matches[1]);
                if (
                    $strict_mode &&
                    (!array_key_exists($main_domain, $arr_whitelist) ||
                        $arr_whitelist[$main_domain] >= 1)
                ) {
                    $arr_domains[$main_domain] = [$main_domain];
                } else {
                    $arr_domains[$main_domain][] = $row;
                }
            }
        }

        return $arr_domains;
    }

    /**
     * 从hosts或dnsmasq类文件中提取地址
     *
     * @param String $str_hosts 原始的hosts字符串
     * @param Boolean $strict_mode 严格模式，启用时将屏蔽该域所在的主域名，例如www.baidu.com，将获取到baidu.com并写入最终列表
     * @param Array $arr_whitelist 白名单
     * @return array
     */
    public static function get_domain_list(
        $str_hosts,
        $strict_mode = false,
        $arr_whitelist = []
    ) {
        $strlen = strlen($str_hosts);
        if ($strlen < 3) {
            return [];
        }

        $str_hosts = $str_hosts . "\n"; //防止最后一行没有换行符

        $i = 0;
        $arr_domains = [];
        while ($i < $strlen) {
            $end_pos = strpos($str_hosts, "\n", $i);
            $line = trim(substr($str_hosts, $i, $end_pos - $i));
            $i = $end_pos + 1;
            if (empty($line) || $line[0] == "#") {
                //注释行忽略
                continue;
            }
            $line = strtolower(preg_replace('/[\s\t]+/', "/", $line));

            if (
                strpos($line, "127.0.0.1") === false &&
                strpos($line, "::") === false &&
                strpos($line, "0.0.0.0") === false
            ) {
                continue;
            }

            $row = explode("/", $line);
            if (strpos($row[1], ".") === false) {
                continue;
            }
            $main_domain = self::extract_main_domain($row[1]);
            if (
                $strict_mode &&
                (!array_key_exists($main_domain, $arr_whitelist) ||
                    $arr_whitelist[$main_domain] >= 1)
            ) {
                $arr_domains[$main_domain] = [$main_domain];
            } else {
                $arr_domains[$main_domain][] = $row[1];
            }
        }

        return $arr_domains;
    }

    private static function write_conf_header($fp, $header, $arr_params = [])
    {
        $header = str_replace("{DATE}", date("YmdHis"), $header);
        $header = str_replace("{URL}", self::LINK_URL, $header);

        foreach ($arr_params as $keyword => $val) {
            $header = str_replace("{" . $keyword . "}", $val, $header);
        }
        return fwrite($fp, $header);
    }

    /**
     * 写入结果到最终文件
     *
     * @param array $arr_src
     * @param $arr_format
     * @param array $arr_whitelist
     * @return false|int
     */
    public static function write_to_file(
        array $arr_src,
        array $arr_format,
        array $arr_whitelist = []
    ) {
        if (count($arr_src) < 1) {
            return false;
        }

        foreach ($arr_whitelist as $wlk => $wlv) {
            if (-1 === $wlv) {
                unset($arr_whitelist[$wlk]);
            }
        }

        $str_result = "";
        $line_count = 0;

        $arr_written = [];
        foreach ($arr_src as $main_domain => $arr_subdomains) {
            if (
                array_key_exists($main_domain, $arr_whitelist) &&
                $arr_whitelist[$main_domain] > 0
            ) {
                continue;
            }

            if (empty($main_domain)) {
                //不匹配记录（一般是不合法域名或者未收录的后缀）
                continue;
            }

            if (
                1 !== $arr_format["full_domain"] &&
                !array_key_exists($main_domain, $arr_whitelist) &&
                (in_array($main_domain, $arr_subdomains) ||
                    in_array("www." . $main_domain, $arr_subdomains) ||
                    in_array("." . $main_domain, $arr_subdomains))
            ) {
                $str_result .=
                    str_replace(
                        "{DOMAIN}",
                        $main_domain,
                        $arr_format["format"]
                    ) . "\n";
                $line_count++;
                continue;
            }

            $arr_subdomains = array_fill_keys($arr_subdomains, 2);

            foreach ($arr_subdomains as $subdomain => $__) {
                if (array_key_exists($subdomain, $arr_whitelist)) {
                    continue;
                }

                $arr_tmp_domain = explode(".", $subdomain);
                $tmp_domain_len = count($arr_tmp_domain);
                if ($tmp_domain_len < 3) {
                    $str_result .=
                        str_replace(
                            "{DOMAIN}",
                            $subdomain,
                            $arr_format["format"]
                        ) . "\n";
                    $line_count++;
                    $arr_written[$subdomain] = 2;
                    continue;
                }

                $matched_flag = false;
                for ($pos = 3; $pos <= $tmp_domain_len; $pos++) {
                    $arr_tmp = array_slice($arr_tmp_domain, -1 * $pos);
                    $tmp = implode(".", $arr_tmp);

                    if (array_key_exists($tmp, $arr_whitelist)) {
                        $matched_flag = $arr_whitelist[$tmp] === 1;
                        break;
                    }

                    if (
                        $tmp === $subdomain ||
                        array_key_exists($tmp, $arr_subdomains)
                    ) {
                        if (!array_key_exists($tmp, $arr_written)) {
                            $str_result .=
                                str_replace(
                                    "{DOMAIN}",
                                    $tmp,
                                    $arr_format["format"]
                                ) . "\n";
                            $line_count++;
                            $arr_written[$tmp] = 2;
                        }
                        $matched_flag = 1 !== $arr_format["full_domain"];
                        break;
                    }
                }

                if ($matched_flag) {
                    continue;
                }

                if (!array_key_exists($subdomain, $arr_written)) {
                    $str_result .=
                        str_replace(
                            "{DOMAIN}",
                            $subdomain,
                            $arr_format["format"]
                        ) . "\n";
                    $line_count++;
                    $arr_written[$subdomain] = 3;
                }
            }
        }
        unset($arr_written);

        $fp = fopen(ROOT_DIR . $arr_format["filename"], "w");
        $write_len = self::write_conf_header($fp, $arr_format["header"], [
            "COUNT" => $line_count,
        ]);
        $write_len += fwrite($fp, $str_result);
        return $write_len;
    }
}
