protected static function compileDirective(string $directive, $policies): string
    {
        // handle special directive first
        switch ($directive) {
            // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/plugin-types
            case 'plugin-types':
                return empty($policies) ? '' : sprintf('%s %s', $directive, implode(' ', $policies));

            // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/require-sri-for
            // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/sandbox
            case 'require-sri-for':
            case 'sandbox':
                return empty($policies) ? '' : sprintf('%s %s', $directive, $policies);
        }

        // when policies is empty, we assume that user disallow this directive
        if (empty($policies)) {
            return sprintf("%s 'none'", $directive);
        }

        $ret = [$directive];

        // keyword source, https://www.w3.org/TR/CSP/#grammardef-keyword-source, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
        foreach (['self', 'unsafe-inline', 'unsafe-eval', 'strict-dynamic', 'unsafe-hashed-attributes', 'report-sample'] as $keyword) {
            if (! empty($policies[$keyword])) {
                $ret[] = sprintf("'%s'", $keyword);
            }
        }

        if (! empty($policies['allow'])) {
            foreach ($policies['allow'] as $url) {
                // removes illegal URL characters
                if (false !== ($url = filter_var($url, FILTER_SANITIZE_URL))) {
                    $ret[] = $url;
                }
            }
        }

        if (! empty($policies['hashes'])) {
            foreach ($policies['hashes'] as $algo => $hashes) {
                // skip not support algorithm, https://www.w3.org/TR/CSP/#grammardef-hash-source
                if (! in_array($algo, ['sha256', 'sha384', 'sha512'])) {
                    continue;
                }

                foreach ($hashes as $value) {
                    // skip invalid value
                    if (! self::isBase64Valid($value)) {
                        continue;
                    }

                    $ret[] = sprintf("'%s-%s'", $algo, $value);
                }
            }
        }

        if (! empty($policies['nonces'])) {
            foreach ($policies['nonces'] as $nonce) {
                // skip invalid value, https://www.w3.org/TR/CSP/#grammardef-nonce-source
                if (! self::isBase64Valid($nonce)) {
                    continue;
                }

                $ret[] = sprintf("'nonce-%s'", $nonce);
            }
        }

        if (! empty($policies['schemes'])) {
            foreach ($policies['schemes'] as $scheme) {
                $ret[] = sprintf('%s', $scheme);
            }
        }

        return implode(' ', $ret);
    }