import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.Strictness;

/**
 * HONEST Full-Width Unicode Digit Test for Gson
 *
 * Based on actual Gson JsonReader.java source analysis:
 *
 * HOW GSON ACTUALLY PARSES NUMBERS:
 * 1. peekNumber() scans unquoted tokens using strict ASCII check: c < '0' || c > '9'
 *    → Full-width digits in UNQUOTED position are rejected by peekNumber()
 *    → They fall through to isLiteral() which returns TRUE (they're not delimiters)
 *    → So they become PEEKED_UNQUOTED (treated as an unquoted string literal)
 *
 * 2. When target field is `int`, nextInt() is called:
 *    → If token is PEEKED_DOUBLE_QUOTED/SINGLE_QUOTED/UNQUOTED, it extracts the string
 *    → Then calls Integer.parseInt(peekedString)
 *    → Integer.parseInt() in Java DOES NOT accept full-width digits → throws NFE
 *    → Gson falls back to Double.parseDouble() → also throws NFE
 *    → Gson wraps it and throws JsonSyntaxException
 *
 * WHAT WE'RE ACTUALLY TESTING:
 * - Quoted full-width digits targeting int field   → expect EXCEPTION (secure)
 * - Unquoted full-width digits targeting int field → expect EXCEPTION (secure)
 * - Mixed content in String field                  → expect raw Unicode preserved
 * - Behavior difference between Gson versions/strictness modes
 *
 * HOW TO RUN:
 *   javac -cp gson-2.13.1.jar GsonFullWidthHonestTest.java
 *   java  -cp gson-2.13.1.jar:. com.example.gson.test.GsonFullWidthHonestTest
 *
 * Download Gson: https://repo1.maven.org/maven2/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar
 */
public class GsonFullWidthHonestTest {

    // ── Target model classes ───────────────────────────────────────────────

    static class IntField      { public int value; }
    static class LongField     { public long value; }
    static class DoubleField   { public double value; }
    static class StringField   { public String value; }

    // ── Helpers ────────────────────────────────────────────────────────────

    static String unicode(String s) {
        StringBuilder sb = new StringBuilder();
        for (char c : s.toCharArray()) {
            if (c > 127) sb.append(String.format("U+%04X", (int) c));
            else         sb.append(c);
        }
        return sb.toString();
    }

    static void section(String title) {
        System.out.println("\n" + "─".repeat(72));
        System.out.println("  " + title);
        System.out.println("─".repeat(72));
    }

    static void tryParse(String label, String json, Class<?> targetClass, Gson gson) {
        System.out.printf("  %-42s │ ", label);
        try {
            Object result = gson.fromJson(json, targetClass);
            // Use reflection to grab .value field
            Object val = result.getClass().getField("value").get(result);
            System.out.printf("PARSED  → %s%n", val);
        } catch (JsonSyntaxException e) {
            // Trim the message to the first meaningful part
            String msg = e.getMessage();
            if (msg != null && msg.length() > 80) msg = msg.substring(0, 80) + "...";
            System.out.printf("EXCEPTION → %s%n", msg);
        } catch (Exception e) {
            System.out.printf("ERROR   → %s%n", e.getMessage());
        }
    }

    // ── Tests ──────────────────────────────────────────────────────────────

    public static void main(String[] args) {
        System.out.println("╔══════════════════════════════════════════════════════════════════════╗");
        System.out.println("║     HONEST GSON FULL-WIDTH UNICODE DIGIT TEST                        ║");
        System.out.println("║     Target: com.google.gson (latest available)                       ║");
        System.out.println("╚══════════════════════════════════════════════════════════════════════╝");

        Gson defaultGson  = new Gson();
        Gson lenientGson  = new GsonBuilder().setLenient().create();
        // Gson 2.10+ only — comment out if using older version:
        Gson strictGson   = new GsonBuilder().setStrictness(Strictness.STRICT).create();

        // ── SECTION 1: Quoted full-width digits → int field ────────────────
        // JSON spec: "value" field is a JSON string (quoted).
        // Gson's nextInt() will extract the string then call Integer.parseInt().
        // Java's Integer.parseInt() rejects full-width digits entirely.
        // Expected: JsonSyntaxException on all of these.

        section("1. QUOTED full-width string → int field  (expect: EXCEPTION)");
        System.out.println("  Rationale: parseInt() strictly rejects non-ASCII digit codepoints.");
        System.out.printf("  %-42s │ %s%n", "Input", "Result");
        System.out.println("  " + "─".repeat(68));

        String[] quotedCases = {
            "{\"value\": \"１２３４５６\"}",    // all full-width
            "{\"value\": \"３５６４５４\"}",    // different number
            "{\"value\": \"１\"}",             // single digit
            "{\"value\": \"１２３\"}",         // partial
            "{\"value\": \"1２3\"}",           // mixed ASCII + full-width
        };
        for (String json : quotedCases) {
            tryParse(json, json, IntField.class, defaultGson);
        }

        // ── SECTION 2: Unquoted full-width digits → int field ──────────────
        // Full-width chars are NOT delimiters, so isLiteral() = true.
        // peekNumber() rejects them (strict ASCII check).
        // They become PEEKED_UNQUOTED. nextInt() tries parseInt → throws.
        // Expected: JsonSyntaxException (or MalformedJsonException in strict mode).

        section("2. UNQUOTED full-width digits → int field  (expect: EXCEPTION)");
        System.out.println("  Rationale: peekNumber() uses c < '0' || c > '9'. Full-width fail this.");
        System.out.printf("  %-42s │ %s%n", "Input", "Result");
        System.out.println("  " + "─".repeat(68));

        String[] unquotedCases = {
            "{\"value\": １２３４５６}",   // no quotes around full-width digits
            "{\"value\": ３５６}",
            "{\"value\": １}",
        };
        for (String json : unquotedCases) {
            tryParse("DEFAULT  " + json, json, IntField.class, defaultGson);
            tryParse("LENIENT  " + json, json, IntField.class, lenientGson);
        }

        // ── SECTION 3: Quoted full-width digits → String field ─────────────
        // When target is String, Gson just returns the raw string content.
        // No parseInt involved. Full-width chars preserved as-is.
        // Expected: PARSED, value contains full-width Unicode.
        // This is NOT a vulnerability — it's expected String behavior.

        section("3. QUOTED full-width digits → String field  (expect: PARSED as-is)");
        System.out.println("  Rationale: No numeric conversion. String field just holds the raw chars.");
        System.out.printf("  %-42s │ %s%n", "Input", "Result");
        System.out.println("  " + "─".repeat(68));

        String[] stringCases = {
            "{\"value\": \"１２３４５６\"}",
            "{\"value\": \"abc１２３def\"}",
            "{\"value\": \"1２3４5\"}",
        };
        for (String json : stringCases) {
            tryParse(json, json, StringField.class, defaultGson);
        }

        // ── SECTION 4: Control — ASCII digits → int field ──────────────────
        // Baseline. These must work. If they don't, your Gson setup is broken.

        section("4. CONTROL: ASCII digits → int field  (expect: PARSED correctly)");
        System.out.printf("  %-42s │ %s%n", "Input", "Result");
        System.out.println("  " + "─".repeat(68));

        String[] asciiCases = {
            "{\"value\": 123456}",         // unquoted integer (standard JSON)
            "{\"value\": \"123456\"}",     // quoted integer string
            "{\"value\": -99}",
            "{\"value\": 0}",
        };
        for (String json : asciiCases) {
            tryParse(json, json, IntField.class, defaultGson);
        }

        // ── SECTION 5: What Integer.parseInt actually does ─────────────────
        // This is the ground truth. Run this independently of Gson.
        // Confirms our understanding of why the bug does NOT exist.

        section("5. RAW Java Integer.parseInt() behavior  (no Gson involved)");
        System.out.println("  This shows exactly what Gson's fallback calls.");
        System.out.println();

        String[] parseTargets = {
            "123456",        // ASCII — must work
            "１２３４５６",  // full-width — must throw
            "1２3",          // mixed — must throw
            "١٢٣",          // Arabic-Indic digits U+0661-U+0663 — must throw
            "১২৩",          // Bengali digits — must throw
        };

        for (String s : parseTargets) {
            System.out.printf("  Integer.parseInt(%-20s) │ ", "\"" + unicode(s) + "\"");
            try {
                int result = Integer.parseInt(s);
                System.out.printf("RESULT: %d%n", result);
            } catch (NumberFormatException e) {
                System.out.printf("NumberFormatException (expected for non-ASCII)%n");
            }
        }

        // ── SECTION 6: Strictness mode comparison (Gson 2.10+) ─────────────
        // Tests whether strict mode changes behavior for these inputs.
        // In strict mode, unquoted non-standard tokens are rejected harder.

        section("6. STRICTNESS MODE comparison on quoted full-width → int");
        System.out.println("  NOTE: Strictness.STRICT requires Gson 2.10+. Comment out if older.");
        System.out.printf("  %-42s │ %s%n", "Mode + Input", "Result");
        System.out.println("  " + "─".repeat(68));

        String testJson = "{\"value\": \"１２３\"}";
        tryParse("DEFAULT  " + testJson, testJson, IntField.class, defaultGson);
        tryParse("LENIENT  " + testJson, testJson, IntField.class, lenientGson);
        tryParse("STRICT   " + testJson, testJson, IntField.class, strictGson);

        // ── SUMMARY ────────────────────────────────────────────────────────
        System.out.println("\n" + "═".repeat(72));
        System.out.println("  WHAT TO LOOK FOR IN YOUR OUTPUT:");
        System.out.println("═".repeat(72));
        System.out.println();
        System.out.println("  Section 1 & 2: ALL lines should say EXCEPTION.");
        System.out.println("    → If ANY line says PARSED with an integer value,");
        System.out.println("      THAT is your confirmed vulnerability. Note the exact");
        System.out.println("      Gson version and input string for the report.");
        System.out.println();
        System.out.println("  Section 3: ALL lines should say PARSED.");
        System.out.println("    → This is expected/normal. NOT a vulnerability.");
        System.out.println("      String fields holding Unicode is correct behavior.");
        System.out.println();
        System.out.println("  Section 4: ALL lines should say PARSED.");
        System.out.println("    → If these fail, your Gson jar is broken/wrong version.");
        System.out.println();
        System.out.println("  Section 5: Only the first line (ASCII) should say RESULT.");
        System.out.println("    → All others should say NumberFormatException.");
        System.out.println("    → If any non-ASCII input PARSES here, that's a JDK bug,");
        System.out.println("      not a Gson bug — and is extremely unlikely.");
        System.out.println();
        System.out.println("  Gson version you should test against: 2.13.1 (latest stable)");
        System.out.println("  Download: https://repo1.maven.org/maven2/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar");
        System.out.println("═".repeat(72));
    }
}