#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission

Encoding / decoding / serialization round-trips and known vectors.
Covers: hex, base64 (std + url-safe), DBMS hex decode, byte<->text conversion,
JSON (de)serialization, restricted base64-pickle.
"""

import os
import random
import sys
import unittest

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _testutils import bootstrap
bootstrap()

from lib.core.convert import (decodeHex, encodeHex, decodeBase64, encodeBase64,
                              getBytes, getText, getUnicode, getOrds,
                              jsonize, dejsonize, serializeValue, deserializeValue)
from lib.core.common import decodeDbmsHexValue

try:
    unichr = unichr
except NameError:
    unichr = chr

RND = random.Random(0xC0FFEE)


def _rand_bytes(maxlen=48):
    return bytes(bytearray(RND.randint(0, 255) for _ in range(RND.randint(0, maxlen))))


class TestHex(unittest.TestCase):
    def test_known_vectors(self):
        self.assertEqual(decodeHex("31323334", binary=True), b"1234")
        self.assertEqual(getText(encodeHex(b"1234", binary=False)), "31323334")

    def test_roundtrip_property(self):
        for _ in range(3000):
            raw = _rand_bytes()
            self.assertEqual(decodeHex(encodeHex(raw, binary=False), binary=True), raw)


class TestBase64(unittest.TestCase):
    def test_known_vectors(self):
        self.assertEqual(decodeBase64("MTIz", binary=True), b"123")
        self.assertEqual(decodeBase64("MTIzNA", binary=True), b"1234")      # missing padding
        self.assertEqual(decodeBase64("MTIzNA==", binary=True), b"1234")
        self.assertEqual(getText(encodeBase64(b"123", binary=False)), "MTIz")
        # url-safe and standard alphabets must decode equivalently
        self.assertEqual(decodeBase64("A-B_CDE", binary=True), decodeBase64("A+B/CDE", binary=True))

    def test_roundtrip_property(self):
        for _ in range(3000):
            raw = _rand_bytes()
            self.assertEqual(decodeBase64(encodeBase64(raw, binary=True), binary=True), raw)
            self.assertEqual(decodeBase64(encodeBase64(raw, binary=True, safe=True), binary=True), raw)
            self.assertEqual(decodeBase64(encodeBase64(raw, binary=True, padding=False), binary=True), raw)


class TestDecodeDbmsHexValue(unittest.TestCase):
    # authoritative vectors taken from the function's own doctests
    def test_known_vectors(self):
        self.assertEqual(decodeDbmsHexValue("3132332031"), u"123 1")
        self.assertEqual(decodeDbmsHexValue("31003200330020003100"), u"123 1")  # utf-16-le shaped
        self.assertEqual(decodeDbmsHexValue("00310032003300200031"), u"123 1")  # utf-16-be shaped
        self.assertEqual(decodeDbmsHexValue("0x31003200330020003100"), u"123 1")
        self.assertEqual(decodeDbmsHexValue("313233203"), u"123 ?")             # odd length
        self.assertEqual(decodeDbmsHexValue(["0x31", "0x32"]), [u"1", u"2"])    # list input

    def test_ascii_roundtrip_property(self):
        for _ in range(1000):
            s = "".join(chr(RND.randint(0x20, 0x7e)) for _ in range(RND.randint(1, 30)))
            if len(s) % 2 == 0:  # avoid the deliberate odd-length '?' behavior
                self.assertEqual(decodeDbmsHexValue(getText(encodeHex(getBytes(s), binary=False))), s)


class TestByteTextConversion(unittest.TestCase):
    def test_ascii_roundtrip(self):
        for _ in range(1000):
            s = u"".join(unichr(RND.randint(0x20, 0x7e)) for _ in range(RND.randint(0, 30)))
            self.assertEqual(getUnicode(getBytes(s)), s)

    def test_unicode_roundtrip(self):
        samples = [u"café", u"你好", u"\U0001F600", u"a’b™c"]
        for s in samples:
            self.assertEqual(getUnicode(getBytes(s)), s)

    def test_getords(self):
        self.assertEqual(getOrds(b"AB"), [65, 66])


class TestJson(unittest.TestCase):
    def test_roundtrip(self):
        for obj in [{"a": 1, "b": [1, 2, 3]}, [1, "x", None], {"nested": {"k": "v"}}, "str", 123]:
            self.assertEqual(dejsonize(jsonize(obj)), obj)

    def test_jsonize_produces_text_not_identity(self):
        # anchor: jsonize must serialize to a JSON string, not pass the object through
        out = jsonize({"a": 1})
        self.assertIsInstance(out, str)
        self.assertIn('"a"', out)
        self.assertEqual(jsonize(123), "123")          # int -> textual "123"


class TestSerialize(unittest.TestCase):
    # Smoke coverage of the safe (JSON-based) session serializer; the exhaustive corner-case
    # and security suite lives in tests/test_serialize.py.
    def test_roundtrip_allowed_types(self):
        for obj in [[1, 2, 3], {"a": 1}, (1, 2), "text", 42, 3.14, True, None, {"k": [1, {"n": "v"}]}]:
            self.assertEqual(deserializeValue(serializeValue(obj)), obj)

    def test_bytes_roundtrip(self):
        for raw in [b"x", b"\x00\x01\xff", b"\xde\xad\xbe\xef"]:
            self.assertEqual(deserializeValue(serializeValue(raw)), raw, msg="bytes round-trip %r" % raw)

    def test_bytes_nested_in_container_roundtrip(self):
        for obj in [{"a": b"bytes"}, [b"ab", "s", 1, None], ("t", b"\xde\xad")]:
            self.assertEqual(deserializeValue(serializeValue(obj)), obj, msg="nested-bytes round-trip %r" % (obj,))

    def test_output_is_plain_ascii_text_no_base64(self):
        # the serialized form must be readable JSON text (not Base64 / binary) so it lands verbatim in
        # the TEXT session column with zero wrapping overhead
        out = serializeValue({"k": [1, (2, 3)]})
        self.assertIsInstance(out, str)
        self.assertTrue(out.startswith("{") and out.endswith("}"), out)

    def test_deserialize_accepts_bytes(self):
        # BigArray hands the serialized data back as bytes (off a compressed disk chunk)
        self.assertEqual(deserializeValue(getBytes(serializeValue([1, (2, 3)]))), [1, (2, 3)])


if __name__ == "__main__":
    unittest.main(verbosity=2)
