#
# SPDX-License-Identifier: Apache-2.0
r"""
=====================================================
B613: TrojanSource - Bidirectional control characters
=====================================================

This plugin checks for the presence of unicode bidirectional control characters
in Python source files. Those characters can be embedded in comments and strings
to reorder source code characters in a way that changes its logic.

:Example:

.. code-block:: none

    >> Issue: [B613:trojansource] A Python source file contains bidirectional control characters ('\u202e').
       Severity: High   Confidence: Medium
       CWE: CWE-838 (https://cwe.mitre.org/data/definitions/838.html)
       More Info: https://bandit.readthedocs.io/en/1.7.5/plugins/b113_trojansource.html
       Location: examples/trojansource.py:4:25
     3  	access_level = "user"
     4	    if access_level != 'none‮⁦': # Check if admin ⁩⁦' and access_level != 'user
     5	        print("You are an admin.\n")

.. seealso::

 - https://trojansource.codes/
 - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42574

.. versionadded:: 1.7.10

"""  # noqa: E501
from tokenize import detect_encoding

import bandit
from bandit.core import issue
from bandit.core import test_properties as test


BIDI_CHARACTERS = (
    "\u202a",
    "\u202b",
    "\u202c",
    "\u202d",
    "\u202e",
    "\u2066",
    "\u2067",
    "\u2068",
    "\u2069",
    "\u200f",
)


@test.test_id("B613")
@test.checks("File")
def trojansource(context):
    src_data = context.file_data
    src_data.seek(0)
    encoding, _ = detect_encoding(src_data.readline)
    src_data.seek(0)
    for lineno, line in enumerate(
        src_data.read().decode(encoding).splitlines(), start=1
    ):
        for char in BIDI_CHARACTERS:
            try:
                col_offset = line.index(char) + 1
            except ValueError:
                continue
            text = (
                "A Python source file contains bidirectional"
                " control characters (%r)." % char
            )
            b_issue = bandit.Issue(
                severity=bandit.HIGH,
                confidence=bandit.MEDIUM,
                cwe=issue.Cwe.INAPPROPRIATE_ENCODING_FOR_OUTPUT_CONTEXT,
                text=text,
                lineno=lineno,
                col_offset=col_offset,
            )
            b_issue.linerange = [lineno]
            return b_issue
