glasswall.libraries.editor.editor

   1import ctypes as ct
   2import functools
   3import io
   4import os
   5from contextlib import contextmanager
   6from typing import Optional, Union
   7
   8import glasswall
   9from glasswall import determine_file_type as dft
  10from glasswall import utils
  11from glasswall.config.logging import format_object, log
  12from glasswall.libraries.editor import errors, successes
  13from glasswall.libraries.library import Library
  14
  15
  16class Editor(Library):
  17    """ A high level Python wrapper for Glasswall Editor / Core2. """
  18
  19    def __init__(self, library_path: str, licence: Union[str, bytes, bytearray, io.BytesIO] = None):
  20        """ Initialise the Editor instance.
  21
  22        Args:
  23            library_path (str): The file or directory path to the Editor library.
  24            licence (str, bytes, bytearray, or io.BytesIO, optional): The licence file content or path. This can be:
  25                - A string representing the file path to the licence.
  26                - A `bytes` or `bytearray` object containing the licence data.
  27                - An `io.BytesIO` object for in-memory licence data.
  28                If not specified, it is assumed that the licence file is located in the same directory as the `library_path`.
  29        """
  30        super().__init__(library_path)
  31        self.library = self.load_library(os.path.abspath(library_path))
  32        self.licence = licence
  33
  34        # Validate killswitch has not activated
  35        self.validate_licence()
  36
  37        log.info(f"Loaded Glasswall {self.__class__.__name__} version {self.version()} from {self.library_path}")
  38
  39    def validate_licence(self):
  40        """ Validates the licence of the library by checking the licence details.
  41
  42        Raises:
  43            LicenceExpired: If the licence has expired or could not be validated.
  44        """
  45        licence_details = self.licence_details()
  46
  47        bad_details = [
  48            "Unable to Read Licence Key File",
  49            "Licence File Missing Required Contents",
  50            "Licence Expired",
  51        ]
  52
  53        if any(bad_detail.lower() in licence_details.lower() for bad_detail in bad_details):
  54            # bad_details found in licence_details
  55            log.error(f"{self.__class__.__name__} licence validation failed. Licence details:\n{licence_details}")
  56            raise errors.LicenceExpired(licence_details)
  57        else:
  58            log.debug(f"{self.__class__.__name__} licence validated successfully. Licence details:\n{licence_details}")
  59
  60    def version(self):
  61        """ Returns the Glasswall library version.
  62
  63        Returns:
  64            version (str): The Glasswall library version.
  65        """
  66        # API function declaration
  67        self.library.GW2LibVersion.restype = ct.c_char_p
  68
  69        # API call
  70        version = self.library.GW2LibVersion()
  71
  72        # Convert to Python string
  73        version = ct.string_at(version).decode()
  74
  75        return version
  76
  77    def open_session(self):
  78        """ Open a new Glasswall session.
  79
  80        Returns:
  81            session (int): An incrementing integer repsenting the current session.
  82        """
  83        # API call
  84        session = self.library.GW2OpenSession()
  85
  86        log.debug(f"\n\tsession: {session}")
  87
  88        if self.licence:
  89            # Must register licence on each GW2OpenSession if the licence is not in the same directory as the library
  90            self.register_licence(session, self.licence)
  91
  92        return session
  93
  94    def close_session(self, session: int) -> int:
  95        """ Close the Glasswall session. All resources allocated by the session will be destroyed.
  96
  97        Args:
  98            session (int): The session to close.
  99
 100        Returns:
 101            status (int): The status code of the function call.
 102        """
 103        if not isinstance(session, int):
 104            raise TypeError(session)
 105
 106        # API function declaration
 107        self.library.GW2CloseSession.argtypes = [ct.c_size_t]
 108
 109        # Variable initialisation
 110        ct_session = ct.c_size_t(session)
 111
 112        # API call
 113        status = self.library.GW2CloseSession(ct_session)
 114
 115        if status not in successes.success_codes:
 116            log.error(f"\n\tsession: {session}\n\tstatus: {status}")
 117        else:
 118            log.debug(f"\n\tsession: {session}\n\tstatus: {status}")
 119
 120        return status
 121
 122    @contextmanager
 123    def new_session(self):
 124        """ Context manager. Opens a new session on entry and closes the session on exit. """
 125        try:
 126            session = self.open_session()
 127            yield session
 128        finally:
 129            self.close_session(session)
 130
 131    def run_session(self, session):
 132        """ Runs the Glasswall session and begins processing of a file.
 133
 134        Args:
 135            session (int): The session to run.
 136
 137        Returns:
 138            status (int): The status code of the function call.
 139        """
 140        # API function declaration
 141        self.library.GW2RunSession.argtypes = [ct.c_size_t]
 142
 143        # Variable initialisation
 144        ct_session = ct.c_size_t(session)
 145
 146        # API call
 147        status = self.library.GW2RunSession(ct_session)
 148
 149        if status not in successes.success_codes:
 150            log.error(f"\n\tsession: {session}\n\tstatus: {status}\n\tGW2FileErrorMsg: {self.file_error_message(session)}")
 151        else:
 152            log.debug(f"\n\tsession: {session}\n\tstatus: {status}\n\tGW2FileErrorMsg: {self.file_error_message(session)}")
 153
 154        return status
 155
 156    def determine_file_type(self, input_file: Union[str, bytes, bytearray, io.BytesIO], as_string: bool = False, raise_unsupported: bool = True) -> Union[int, str]:
 157        """ Determine the file type of a given input file, either as an integer identifier or a string.
 158
 159        Args:
 160            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file to analyse. It can be provided as a file path (str), bytes, bytearray, or a BytesIO object.
 161            as_string (bool, optional): Return file type as string, eg: "bmp" instead of: 29. Defaults to False.
 162            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
 163
 164        Returns:
 165            file_type (Union[int, str]): The file type.
 166        """
 167        if isinstance(input_file, str):
 168            if not os.path.isfile(input_file):
 169                raise FileNotFoundError(input_file)
 170
 171            # convert to ct.c_char_p of bytes
 172            ct_input_file = ct.c_char_p(input_file.encode("utf-8"))
 173
 174            # API call
 175            file_type = self.library.GW2DetermineFileTypeFromFile(ct_input_file)
 176
 177        elif isinstance(input_file, (bytes, bytearray, io.BytesIO)):
 178            # convert to bytes
 179            bytes_input_file = utils.as_bytes(input_file)
 180
 181            # ctypes conversion
 182            ct_buffer = ct.c_char_p(bytes_input_file)
 183            ct_buffer_length = ct.c_size_t(len(bytes_input_file))
 184
 185            # API call
 186            file_type = self.library.GW2DetermineFileTypeFromMemory(
 187                ct_buffer,
 188                ct_buffer_length
 189            )
 190
 191        else:
 192            raise TypeError(input_file)
 193
 194        file_type_as_string = dft.file_type_int_to_str(file_type)
 195        input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
 196
 197        if not dft.is_success(file_type):
 198            log.warning(f"\n\tfile_type: {file_type}\n\tfile_type_as_string: {file_type_as_string}\n\tinput_file: {input_file_repr}")
 199            if raise_unsupported:
 200                raise dft.int_class_map.get(file_type, dft.errors.UnknownErrorCode)(file_type)
 201        else:
 202            log.debug(f"\n\tfile_type: {file_type}\n\tfile_type_as_string: {file_type_as_string}\n\tinput_file: {input_file_repr}")
 203
 204        if as_string:
 205            return file_type_as_string
 206
 207        return file_type
 208
 209    def _GW2GetPolicySettings(self, session: int, policy_format: int = 0):
 210        """ Get current policy settings for the given session.
 211
 212        Args:
 213            session (int): The session integer.
 214            policy_format (int): The format of the content management policy. 0=XML.
 215
 216        Returns:
 217            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'policy_format', 'status', 'policy'.
 218        """
 219        # API function declaration
 220        self.library.GW2GetPolicySettings.argtypes = [
 221            ct.c_size_t,  # Session_Handle session
 222            ct.POINTER(ct.c_void_p),  # char ** policiesBuffer
 223            ct.POINTER(ct.c_size_t),  # size_t * policiesLength
 224            ct.c_int,  # Policy_Format format
 225        ]
 226
 227        # Variable initialisation
 228        gw_return_object = glasswall.GwReturnObj()
 229        gw_return_object.session = ct.c_size_t(session)
 230        gw_return_object.buffer = ct.c_void_p()
 231        gw_return_object.buffer_length = ct.c_size_t()
 232        gw_return_object.policy_format = ct.c_int(policy_format)
 233
 234        # API Call
 235        gw_return_object.status = self.library.GW2GetPolicySettings(
 236            gw_return_object.session,
 237            ct.byref(gw_return_object.buffer),
 238            ct.byref(gw_return_object.buffer_length),
 239            gw_return_object.policy_format
 240        )
 241
 242        # Editor wrote to a buffer, convert it to bytes
 243        policy_bytes = utils.buffer_to_bytes(
 244            gw_return_object.buffer,
 245            gw_return_object.buffer_length
 246        )
 247
 248        gw_return_object.policy = policy_bytes.decode()
 249
 250        return gw_return_object
 251
 252    def get_content_management_policy(self, session: int) -> str:
 253        """ Returns the content management configuration for a given session by querying the engine.
 254
 255        Args:
 256            session (int): The session integer.
 257
 258        Returns:
 259            xml_string (str): The XML string of the current content management configuration.
 260        """
 261        gw_return_object = self._GW2GetPolicySettings(session)
 262
 263        if gw_return_object.status not in successes.success_codes:
 264            log.error(
 265                f"GW2GetPolicySettings failed with status: "
 266                f"{gw_return_object.status}"
 267            )
 268            raise errors.error_codes.get(
 269                gw_return_object.status, errors.UnknownErrorCode
 270            )(gw_return_object.status)
 271
 272        log.debug(
 273            f"GW2GetPolicySettings returned policy:\n"
 274            f"{gw_return_object.policy}"
 275        )
 276
 277        return gw_return_object.policy
 278
 279    def _GW2RegisterPoliciesFile(self, session: int, input_file: str, policy_format: int = 0):
 280        """ Registers the policies to be used by Glasswall when processing files.
 281
 282        Args:
 283            session (int): The session integer.
 284            input_file (str): The content management policy input file path.
 285            policy_format (int): The format of the content management policy. 0=XML.
 286
 287        Returns:
 288            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'policy_format', 'status'.
 289        """
 290        # API function declaration
 291        self.library.GW2RegisterPoliciesFile.argtypes = [
 292            ct.c_size_t,  # Session_Handle session
 293            ct.c_char_p,  # const char *filename
 294            ct.c_int,  # Policy_Format format
 295        ]
 296
 297        # Variable initialisation
 298        gw_return_object = glasswall.GwReturnObj()
 299        gw_return_object.session = ct.c_size_t(session)
 300        gw_return_object.input_file = ct.c_char_p(input_file.encode("utf-8"))
 301        gw_return_object.policy_format = ct.c_int(policy_format)
 302
 303        gw_return_object.status = self.library.GW2RegisterPoliciesFile(
 304            gw_return_object.session,
 305            gw_return_object.input_file,
 306            gw_return_object.policy_format
 307        )
 308
 309        return gw_return_object
 310
 311    def _GW2RegisterPoliciesMemory(self, session: int, input_file: bytes, policy_format: int = 0):
 312        """ Registers the policies in memory to be used by Glasswall when processing files.
 313
 314        Args:
 315            session (int): The session integer.
 316            input_file (str): The content management policy input file bytes.
 317            policy_format (int): The format of the content management policy. 0=XML.
 318
 319        Returns:
 320            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'policy_format', 'status'.
 321        """
 322        # API function declaration
 323        self.library.GW2RegisterPoliciesMemory.argtype = [
 324            ct.c_size_t,  # Session_Handle session
 325            ct.c_char_p,  # const char *policies
 326            ct.c_size_t,  # size_t policiesLength
 327            ct.c_int  # Policy_Format format
 328        ]
 329
 330        # Variable initialisation
 331        gw_return_object = glasswall.GwReturnObj()
 332        gw_return_object.session = ct.c_size_t(session)
 333        gw_return_object.buffer = ct.c_char_p(input_file)
 334        gw_return_object.buffer_length = ct.c_size_t(len(input_file))
 335        gw_return_object.policy_format = ct.c_int(policy_format)
 336
 337        # API Call
 338        gw_return_object.status = self.library.GW2RegisterPoliciesMemory(
 339            gw_return_object.session,
 340            gw_return_object.buffer,
 341            gw_return_object.buffer_length,
 342            gw_return_object.policy_format
 343        )
 344
 345        return gw_return_object
 346
 347    def _resolve_editor_policy(
 348        self,
 349        session: int,
 350        editor_policy: "glasswall.content_management.policies.Editor",
 351    ) -> "glasswall.content_management.policies.policy.Policy":
 352        """ Resolves an Editor policy against the engine's default settings.
 353
 354        Queries the engine for its default policy settings, applies the
 355        Editor's default value to all content switches, then applies any
 356        explicit config overrides on top.
 357
 358        Args:
 359            session (int): The session integer.
 360            editor_policy (glasswall.content_management.policies.Editor):
 361                An Editor policy instance with default and config
 362                attributes.
 363
 364        Returns:
 365            policy (glasswall.content_management.policies.policy.Policy):
 366                A fully resolved Policy object.
 367        """
 368        from glasswall.content_management.config_elements.config_element import ConfigElement
 369        from glasswall.content_management.switches.switch import Switch
 370
 371        # Query engine for default policy settings
 372        engine_xml = self.get_content_management_policy(session)
 373        policy = glasswall.content_management.policies.Policy.from_string(engine_xml)
 374
 375        # Standard content switch values used by file-type config elements
 376        # (e.g. pdfConfig, wordConfig). System switches (sysConfig) use
 377        # different vocabularies ("true"/"false", "sisl"/"xml", etc.) and
 378        # must not be overwritten with a content default.
 379        content_switch_values = {"allow", "disallow", "sanitise"}
 380
 381        # Apply default value to content switches only
 382        if editor_policy.default is not None:
 383            for config_element in policy.config_elements:
 384                for switch in config_element.switches:
 385                    # Only set if default is a valid value for this switch
 386                    if switch.restrict_values and editor_policy.default in switch.restrict_values:
 387                        switch.value = editor_policy.default
 388                    elif switch.value in content_switch_values:
 389                        # No restrict_values defined (base Switch from dynamic parsing).
 390                        # Only override if the current value is a standard content switch
 391                        # value, indicating this is a content switch rather than a system switch.
 392                        switch.value = editor_policy.default
 393
 394        # Apply explicit config overrides
 395        for config_element_name, switches in editor_policy.config.items():
 396            # Find existing config element
 397            matching = [c for c in policy.config_elements if c.name == config_element_name]
 398            if matching:
 399                target = matching[0]
 400                for switch_name, switch_value in switches.items():
 401                    if switch_name.startswith("@"):
 402                        target.attributes[switch_name[1:]] = switch_value
 403                    else:
 404                        target.add_switch(Switch(name=switch_name, value=switch_value))
 405            else:
 406                # Create new config element for unknown config sections
 407                new_ce = ConfigElement(name=config_element_name)
 408                for switch_name, switch_value in switches.items():
 409                    if switch_name.startswith("@"):
 410                        new_ce.attributes[switch_name[1:]] = switch_value
 411                    else:
 412                        new_ce.add_switch(Switch(name=switch_name, value=switch_value))
 413                policy.add_config_element(new_ce)
 414
 415        return policy
 416
 417    def set_content_management_policy(self, session: int, input_file: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None, policy_format=0):
 418        """ Sets the content management policy configuration. If input_file is None then the engine's own default settings are applied unmodified.
 419
 420        Args:
 421            session (int): The session integer.
 422            input_file (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (engine defaults). The content management policy to apply.
 423            policy_format (int): The format of the content management policy. 0=XML.
 424
 425        Returns:
 426            - result (glasswall.GwReturnObj): Depending on the input 'input_file':
 427                - If input_file is a str file path:
 428                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'policy_format', 'status'.
 429
 430                - If input_file is a file in memory:
 431                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'policy_format', 'status'.
 432        """
 433        # Validate type
 434        if not isinstance(session, int):
 435            raise TypeError(session)
 436        if not isinstance(input_file, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
 437            raise TypeError(input_file)
 438        if not isinstance(policy_format, int):
 439            raise TypeError(policy_format)
 440
 441        # Handle None: resolve against engine defaults without overriding any switch values
 442        if input_file is None:
 443            input_file = glasswall.content_management.policies.Editor(default=None)
 444
 445        # Handle Editor policy: resolve against engine defaults
 446        if isinstance(input_file, glasswall.content_management.policies.Editor):
 447            input_file = self._resolve_editor_policy(session, input_file)
 448
 449        # Validate xml content is parsable
 450        utils.validate_xml(input_file)
 451
 452        # From file
 453        if isinstance(input_file, str) and os.path.isfile(input_file):
 454            input_file = os.path.abspath(input_file)
 455
 456            result = self._GW2RegisterPoliciesFile(session, input_file)
 457
 458        # From memory
 459        elif isinstance(input_file, (str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
 460            # Convert bytearray, io.BytesIO to bytes
 461            if isinstance(input_file, (bytearray, io.BytesIO)):
 462                input_file = utils.as_bytes(input_file)
 463            # Convert string xml or Policy to bytes
 464            if isinstance(input_file, (str, glasswall.content_management.policies.policy.Policy)):
 465                input_file = input_file.encode("utf-8")
 466
 467            result = self._GW2RegisterPoliciesMemory(session, input_file)
 468
 469        if result.status not in successes.success_codes:
 470            log.error(format_object(result))
 471            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
 472        else:
 473            log.debug(format_object(result))
 474
 475        return result
 476
 477    def _GW2RegisterInputFile(self, session: int, input_file: str):
 478        """ Register an input file for the given session.
 479
 480        Args:
 481            session (int): The session integer.
 482            input_file (str): The input file path.
 483
 484        Returns:
 485            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'status'.
 486        """
 487        # API function declaration
 488        self.library.GW2RegisterInputFile.argtypes = [
 489            ct.c_size_t,  # Session_Handle session
 490            ct.c_char_p  # const char * inputFilePath
 491        ]
 492
 493        # Variable initialisation
 494        gw_return_object = glasswall.GwReturnObj()
 495        gw_return_object.session = ct.c_size_t(session)
 496        gw_return_object.input_file = ct.c_char_p(input_file.encode("utf-8"))
 497        gw_return_object.input_file_path = input_file
 498
 499        # API call
 500        gw_return_object.status = self.library.GW2RegisterInputFile(
 501            gw_return_object.session,
 502            gw_return_object.input_file
 503        )
 504
 505        return gw_return_object
 506
 507    def _GW2RegisterInputMemory(self, session: int, input_file: bytes):
 508        """ Register an input file in memory for the given session.
 509
 510        Args:
 511            session (int): The session integer.
 512            input_file (bytes): The input file in memory.
 513
 514        Returns:
 515            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
 516        """
 517        # API function declaration
 518        self.library.GW2RegisterInputMemory.argtypes = [
 519            ct.c_size_t,  # Session_Handle session
 520            ct.c_char_p,  # const char * inputFileBuffer
 521            ct.c_size_t,  # size_t inputLength
 522        ]
 523
 524        # Variable initialisation
 525        gw_return_object = glasswall.GwReturnObj()
 526        gw_return_object.session = ct.c_size_t(session)
 527        gw_return_object.buffer = ct.c_char_p(input_file)
 528        gw_return_object.buffer_length = ct.c_size_t(len(input_file))
 529
 530        # API call
 531        gw_return_object.status = self.library.GW2RegisterInputMemory(
 532            gw_return_object.session,
 533            gw_return_object.buffer,
 534            gw_return_object.buffer_length
 535        )
 536
 537        return gw_return_object
 538
 539    def register_input(self, session: int, input_file: Union[str, bytes, bytearray, io.BytesIO]):
 540        """ Register an input file or bytes for the given session.
 541
 542        Args:
 543            session (int): The session integer.
 544            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
 545
 546        Returns:
 547            - result (glasswall.GwReturnObj): Depending on the input 'input_file':
 548                - If input_file is a str file path:
 549                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'status'.
 550
 551                - If input_file is a file in memory:
 552                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
 553        """
 554        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO,)):
 555            raise TypeError(input_file)
 556
 557        if isinstance(input_file, str):
 558            if not os.path.isfile(input_file):
 559                raise FileNotFoundError(input_file)
 560
 561            input_file = os.path.abspath(input_file)
 562
 563            result = self._GW2RegisterInputFile(session, input_file)
 564
 565        elif isinstance(input_file, (bytes, bytearray, io.BytesIO,)):
 566            # Convert bytearray and io.BytesIO to bytes
 567            input_file = utils.as_bytes(input_file)
 568
 569            result = self._GW2RegisterInputMemory(session, input_file)
 570
 571        if result.status not in successes.success_codes:
 572            log.error(format_object(result))
 573            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
 574        else:
 575            log.debug(format_object(result))
 576
 577        return result
 578
 579    def _GW2RegisterOutFile(self, session: int, output_file: str):
 580        """ Register an output file for the given session.
 581
 582        Args:
 583            session (int): The session integer.
 584            output_file (str): The output file path.
 585
 586        Returns:
 587            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.
 588        """
 589        # API function declaration
 590        self.library.GW2RegisterOutFile.argtypes = [
 591            ct.c_size_t,  # Session_Handle session
 592            ct.c_char_p  # const char * outputFilePath
 593        ]
 594
 595        # Variable initialisation
 596        gw_return_object = glasswall.GwReturnObj()
 597        gw_return_object.session = ct.c_size_t(session)
 598        gw_return_object.output_file = ct.c_char_p(output_file.encode("utf-8"))
 599
 600        # API call
 601        gw_return_object.status = self.library.GW2RegisterOutFile(
 602            gw_return_object.session,
 603            gw_return_object.output_file
 604        )
 605
 606        return gw_return_object
 607
 608    def _GW2RegisterOutputMemory(self, session: int):
 609        """ Register an output file in memory for the given session.
 610
 611        Args:
 612            session (int): The session integer.
 613
 614        Returns:
 615            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
 616        """
 617        # API function declaration
 618        self.library.GW2RegisterOutputMemory.argtypes = [
 619            ct.c_size_t,  # Session_Handle session
 620            ct.POINTER(ct.c_void_p),  # char ** outputBuffer
 621            ct.POINTER(ct.c_size_t)  # size_t * outputLength
 622        ]
 623
 624        # Variable initialisation
 625        gw_return_object = glasswall.GwReturnObj()
 626        gw_return_object.session = ct.c_size_t(session)
 627        gw_return_object.buffer = ct.c_void_p()
 628        gw_return_object.buffer_length = ct.c_size_t()
 629
 630        # API call
 631        gw_return_object.status = self.library.GW2RegisterOutputMemory(
 632            gw_return_object.session,
 633            ct.byref(gw_return_object.buffer),
 634            ct.byref(gw_return_object.buffer_length)
 635        )
 636
 637        return gw_return_object
 638
 639    def register_output(self, session, output_file: Optional[str] = None):
 640        """ Register an output file for the given session. If output_file is None the file will be returned as 'buffer' and 'buffer_length' attributes.
 641
 642        Args:
 643            session (int): The session integer.
 644            output_file (Optional[str]): If specified, during run session the file will be written to output_file, otherwise the file will be written to the glasswall.GwReturnObj 'buffer' and 'buffer_length' attributes.
 645
 646        Returns:
 647            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attribute 'status' indicating the result of the function call. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size.
 648        """
 649        if not isinstance(output_file, (type(None), str)):
 650            raise TypeError(output_file)
 651
 652        if isinstance(output_file, str):
 653            output_file = os.path.abspath(output_file)
 654
 655            result = self._GW2RegisterOutFile(session, output_file)
 656
 657        elif isinstance(output_file, type(None)):
 658            result = self._GW2RegisterOutputMemory(session)
 659
 660        if result.status not in successes.success_codes:
 661            log.error(format_object(result))
 662            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
 663        else:
 664            log.debug(format_object(result))
 665
 666        return result
 667
 668    def _GW2RegisterAnalysisFile(self, session: int, output_file: str, analysis_format: int = 0):
 669        """ Register an analysis output file for the given session.
 670
 671        Args:
 672            session (int): The session integer.
 673            output_file (str): The analysis output file path.
 674            analysis_format (int): The format of the analysis report. 0=XML, 1=XMLExtended
 675
 676        Returns:
 677            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'analysis_format', 'status'.
 678        """
 679        # API function declaration
 680        self.library.GW2RegisterAnalysisFile.argtypes = [
 681            ct.c_size_t,  # Session_Handle session
 682            ct.c_char_p,  # const char * analysisFilePathName
 683            ct.c_int,  # Analysis_Format format
 684        ]
 685
 686        # Variable initialisation
 687        gw_return_object = glasswall.GwReturnObj()
 688        gw_return_object.session = ct.c_size_t(session)
 689        gw_return_object.output_file = ct.c_char_p(output_file.encode("utf-8"))
 690        gw_return_object.analysis_format = ct.c_int()
 691
 692        # API call
 693        gw_return_object.status = self.library.GW2RegisterAnalysisFile(
 694            gw_return_object.session,
 695            gw_return_object.output_file,
 696            gw_return_object.analysis_format
 697        )
 698
 699        return gw_return_object
 700
 701    def _GW2RegisterAnalysisMemory(self, session: int, analysis_format: int = 0):
 702        """ Register an analysis output file in memory for the given session.
 703
 704        Args:
 705            session (int): The session integer.
 706            analysis_format (int): The format of the analysis report. 0=XML, 1=XMLExtended
 707
 708        Returns:
 709            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'analysis_format', 'status'.
 710        """
 711        # API function declaration
 712        self.library.GW2RegisterAnalysisMemory.argtypes = [
 713            ct.c_size_t,  # Session_Handle session
 714            ct.POINTER(ct.c_void_p),  # char ** analysisFileBuffer
 715            ct.POINTER(ct.c_size_t),  # size_t * analysisoutputLength
 716            ct.c_int  # Analysis_Format format
 717        ]
 718
 719        # Variable initialisation
 720        gw_return_object = glasswall.GwReturnObj()
 721        gw_return_object.session = ct.c_size_t(session)
 722        gw_return_object.buffer = ct.c_void_p()
 723        gw_return_object.buffer_length = ct.c_size_t()
 724        gw_return_object.analysis_format = ct.c_int()
 725
 726        # API call
 727        gw_return_object.status = self.library.GW2RegisterAnalysisMemory(
 728            gw_return_object.session,
 729            ct.byref(gw_return_object.buffer),
 730            ct.byref(gw_return_object.buffer_length),
 731            gw_return_object.analysis_format
 732        )
 733
 734        return gw_return_object
 735
 736    def register_analysis(self, session: int, output_file: Optional[str] = None):
 737        """ Registers an analysis file for the given session. The analysis file will be created during the session's run_session call.
 738
 739        Args:
 740            session (int): The session integer.
 741            output_file (Optional[str]): Default None. The file path where the analysis will be written. None returns the analysis as bytes.
 742
 743        Returns:
 744            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'status', 'session', 'analysis_format'. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size. If output_file is not None (file mode) 'output_file' is included.
 745        """
 746        if not isinstance(output_file, (type(None), str)):
 747            raise TypeError(output_file)
 748
 749        if isinstance(output_file, str):
 750            output_file = os.path.abspath(output_file)
 751
 752            result = self._GW2RegisterAnalysisFile(session, output_file)
 753
 754        elif isinstance(output_file, type(None)):
 755            result = self._GW2RegisterAnalysisMemory(session)
 756
 757        if result.status not in successes.success_codes:
 758            log.error(format_object(result))
 759            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
 760        else:
 761            log.debug(format_object(result))
 762
 763        return result
 764
 765    def protect_file(
 766        self,
 767        input_file: Union[str, bytes, bytearray, io.BytesIO],
 768        output_file: Optional[str] = None,
 769        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
 770        raise_unsupported: bool = True,
 771    ):
 772        """ Protects a file using the current content management configuration, returning the file bytes. The protected file is written to output_file if it is provided.
 773
 774        Args:
 775            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
 776            output_file (Optional[str]): The output file path where the protected file will be written.
 777            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
 778            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
 779
 780        Returns:
 781            file_bytes (bytes): The protected file bytes.
 782        """
 783        # Validate arg types
 784        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
 785            raise TypeError(input_file)
 786        if not isinstance(output_file, (type(None), str)):
 787            raise TypeError(output_file)
 788        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
 789            raise TypeError(content_management_policy)
 790        if not isinstance(raise_unsupported, bool):
 791            raise TypeError(raise_unsupported)
 792
 793        # Convert string path arguments to absolute paths
 794        if isinstance(input_file, str):
 795            if not os.path.isfile(input_file):
 796                raise FileNotFoundError(input_file)
 797            input_file = os.path.abspath(input_file)
 798        if isinstance(output_file, str):
 799            output_file = os.path.abspath(output_file)
 800            # make directories that do not exist
 801            os.makedirs(os.path.dirname(output_file), exist_ok=True)
 802        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
 803            content_management_policy = os.path.abspath(content_management_policy)
 804
 805        # Convert memory inputs to bytes
 806        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
 807            input_file = utils.as_bytes(input_file)
 808
 809        with utils.CwdHandler(self.library_path):
 810            with self.new_session() as session:
 811                content_management_policy = self.set_content_management_policy(session, content_management_policy)
 812                register_input = self.register_input(session, input_file)
 813                register_output = self.register_output(session, output_file=output_file)
 814                status = self.run_session(session)
 815
 816                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
 817                if status not in successes.success_codes:
 818                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
 819                    if raise_unsupported:
 820                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
 821                    else:
 822                        file_bytes = None
 823                else:
 824                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
 825                    # Get file bytes
 826                    if isinstance(output_file, str):
 827                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
 828                        if not os.path.isfile(output_file):
 829                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
 830                            file_bytes = None
 831                        else:
 832                            with open(output_file, "rb") as f:
 833                                file_bytes = f.read()
 834                    else:
 835                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
 836                        file_bytes = utils.buffer_to_bytes(
 837                            register_output.buffer,
 838                            register_output.buffer_length
 839                        )
 840
 841                # Ensure memory allocated is not garbage collected
 842                content_management_policy, register_input, register_output
 843
 844                return file_bytes
 845
 846    def protect_directory(
 847        self,
 848        input_directory: str,
 849        output_directory: Optional[str] = None,
 850        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
 851        raise_unsupported: bool = True,
 852    ):
 853        """ Recursively processes all files in a directory in protect mode using the given content management policy.
 854        The protected files are written to output_directory maintaining the same directory structure as input_directory.
 855
 856        Args:
 857            input_directory (str): The input directory containing files to protect.
 858            output_directory (Optional[str]): The output directory where the protected file will be written, or None to not write files.
 859            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply.
 860            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
 861
 862        Returns:
 863            protected_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.
 864        """
 865        protected_files_dict = {}
 866        # Call protect_file on each file in input_directory to output_directory
 867        for input_file in utils.list_file_paths(input_directory):
 868            relative_path = os.path.relpath(input_file, input_directory)
 869            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
 870
 871            protected_bytes = self.protect_file(
 872                input_file=input_file,
 873                output_file=output_file,
 874                raise_unsupported=raise_unsupported,
 875                content_management_policy=content_management_policy,
 876            )
 877
 878            protected_files_dict[relative_path] = protected_bytes
 879
 880        return protected_files_dict
 881
 882    def analyse_file(
 883        self,
 884        input_file: Union[str, bytes, bytearray, io.BytesIO],
 885        output_file: Optional[str] = None,
 886        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
 887        raise_unsupported: bool = True,
 888    ):
 889        """ Analyses a file, returning the analysis bytes. The analysis is written to output_file if it is provided.
 890
 891        Args:
 892            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
 893            output_file (Optional[str]): The output file path where the analysis file will be written.
 894            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
 895            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
 896
 897        Returns:
 898            file_bytes (bytes): The analysis file bytes.
 899        """
 900        # Validate arg types
 901        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
 902            raise TypeError(input_file)
 903        if not isinstance(output_file, (type(None), str)):
 904            raise TypeError(output_file)
 905        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
 906            raise TypeError(content_management_policy)
 907        if not isinstance(raise_unsupported, bool):
 908            raise TypeError(raise_unsupported)
 909
 910        # Convert string path arguments to absolute paths
 911        if isinstance(input_file, str):
 912            if not os.path.isfile(input_file):
 913                raise FileNotFoundError(input_file)
 914            input_file = os.path.abspath(input_file)
 915        if isinstance(output_file, str):
 916            output_file = os.path.abspath(output_file)
 917            # make directories that do not exist
 918            os.makedirs(os.path.dirname(output_file), exist_ok=True)
 919        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
 920            content_management_policy = os.path.abspath(content_management_policy)
 921
 922        # Convert memory inputs to bytes
 923        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
 924            input_file = utils.as_bytes(input_file)
 925
 926        with utils.CwdHandler(self.library_path):
 927            with self.new_session() as session:
 928                content_management_policy = self.set_content_management_policy(session, content_management_policy)
 929                register_input = self.register_input(session, input_file)
 930                register_analysis = self.register_analysis(session, output_file)
 931                status = self.run_session(session)
 932
 933                file_bytes = None
 934                if isinstance(output_file, str):
 935                    # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
 936                    if os.path.isfile(output_file):
 937                        with open(output_file, "rb") as f:
 938                            file_bytes = f.read()
 939                else:
 940                    # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
 941                    if register_analysis.buffer and register_analysis.buffer_length:
 942                        file_bytes = utils.buffer_to_bytes(
 943                            register_analysis.buffer,
 944                            register_analysis.buffer_length
 945                        )
 946
 947                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
 948                if status not in successes.success_codes:
 949                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
 950                    if raise_unsupported:
 951                        error = errors.error_codes.get(status, errors.UnknownErrorCode)(status)
 952                        error.analysis_bytes = file_bytes
 953                        raise error
 954                else:
 955                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
 956
 957                # Ensure memory allocated is not garbage collected
 958                content_management_policy, register_input, register_analysis
 959
 960                return file_bytes
 961
 962    def analyse_directory(
 963        self,
 964        input_directory: str,
 965        output_directory: Optional[str] = None,
 966        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
 967        raise_unsupported: bool = True,
 968    ):
 969        """ Analyses all files in a directory and its subdirectories. The analysis files are written to output_directory maintaining the same directory structure as input_directory.
 970
 971        Args:
 972            input_directory (str): The input directory containing files to analyse.
 973            output_directory (Optional[str]): The output directory where the analysis files will be written, or None to not write files.
 974            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply.
 975            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
 976
 977        Returns:
 978            analysis_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.
 979        """
 980        analysis_files_dict = {}
 981        # Call analyse_file on each file in input_directory to output_directory
 982        for input_file in utils.list_file_paths(input_directory):
 983            relative_path = os.path.relpath(input_file, input_directory) + ".xml"
 984            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
 985
 986            analysis_bytes = self.analyse_file(
 987                input_file=input_file,
 988                output_file=output_file,
 989                raise_unsupported=raise_unsupported,
 990                content_management_policy=content_management_policy,
 991            )
 992
 993            analysis_files_dict[relative_path] = analysis_bytes
 994
 995        return analysis_files_dict
 996
 997    def protect_and_analyse_file(
 998        self,
 999        input_file: Union[str, bytes, bytearray, io.BytesIO],
1000        output_file: Optional[str] = None,
1001        output_analysis_report: Optional[str] = None,
1002        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1003        raise_unsupported: bool = True,
1004    ):
1005        """ Protects and analyses a file in a single session, returning both protected file bytes and analysis report bytes.
1006
1007        Args:
1008            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
1009            output_file (Optional[str]): The output file path where the protected file will be written.
1010            output_analysis_report (Optional[str]): The output file path where the XML analysis report will be written.
1011            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1012            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1013
1014        Returns:
1015            Tuple[Optional[bytes], Optional[bytes]]: A tuple of (protected_file_bytes, analysis_report_bytes).
1016        """
1017        # Validate arg types
1018        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1019            raise TypeError(f"input_file expected to be of type: str, bytes, bytearray, io.BytesIO. Got: {type(input_file).__name__}")
1020        if not isinstance(output_file, (type(None), str)):
1021            raise TypeError(f"output_file expected to be of type: None, str. Got: {type(output_file).__name__}")
1022        if not isinstance(output_analysis_report, (type(None), str)):
1023            raise TypeError(f"output_analysis_report expected to be of type: None, str. Got: {type(output_analysis_report).__name__}")
1024        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1025            raise TypeError(f"content_management_policy expected to be of type: None, str, bytes, bytearray, io.BytesIO, Policy. Got: {type(content_management_policy).__name__}")
1026        if not isinstance(raise_unsupported, bool):
1027            raise TypeError(f"raise_unsupported expected to be of type: bool. Got: {type(raise_unsupported).__name__}")
1028
1029        # Convert string path arguments to absolute paths
1030        if isinstance(input_file, str):
1031            if not os.path.isfile(input_file):
1032                raise FileNotFoundError(input_file)
1033            input_file = os.path.abspath(input_file)
1034        if isinstance(output_file, str):
1035            output_file = os.path.abspath(output_file)
1036            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1037        if isinstance(output_analysis_report, str):
1038            output_analysis_report = os.path.abspath(output_analysis_report)
1039            os.makedirs(os.path.dirname(output_analysis_report), exist_ok=True)
1040        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1041            content_management_policy = os.path.abspath(content_management_policy)
1042
1043        # Convert memory inputs to bytes
1044        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1045            input_file = utils.as_bytes(input_file)
1046
1047        with utils.CwdHandler(self.library_path):
1048            with self.new_session() as session:
1049                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1050                register_input = self.register_input(session, input_file)
1051                register_output = self.register_output(session, output_file)
1052                register_analysis = self.register_analysis(session, output_analysis_report)
1053
1054                status = self.run_session(session)
1055
1056                protected_file_bytes = None
1057                analysis_report_bytes = None
1058
1059                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1060                if status not in successes.success_codes:
1061                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1062                    if raise_unsupported:
1063                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1064
1065                # Get analysis report file bytes, even on processing failure
1066                if isinstance(output_analysis_report, str):
1067                    # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1068                    if not os.path.isfile(output_analysis_report):
1069                        log.error(f"Editor returned success code: {status} but no output file was found: {output_analysis_report}")
1070                    else:
1071                        with open(output_analysis_report, "rb") as f:
1072                            analysis_report_bytes = f.read()
1073                else:
1074                    # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1075                    if register_analysis.buffer and register_analysis.buffer_length:
1076                        analysis_report_bytes = utils.buffer_to_bytes(
1077                            register_analysis.buffer,
1078                            register_analysis.buffer_length
1079                        )
1080
1081                # On success, get protected file bytes
1082                if status in successes.success_codes:
1083                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1084                    # Get file bytes
1085                    if isinstance(output_file, str):
1086                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1087                        if not os.path.isfile(output_file):
1088                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1089                        else:
1090                            with open(output_file, "rb") as f:
1091                                protected_file_bytes = f.read()
1092                    else:
1093                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1094                        protected_file_bytes = utils.buffer_to_bytes(
1095                            register_output.buffer,
1096                            register_output.buffer_length
1097                        )
1098
1099                # Ensure memory allocated is not garbage collected
1100                content_management_policy, register_input, register_output, register_analysis
1101
1102                return protected_file_bytes, analysis_report_bytes
1103
1104    def create_config(self, default: Optional[str] = None, config: dict = {}) -> "glasswall.content_management.policies.policy.Policy":
1105        """ Creates a content management policy by querying the engine's default settings and
1106        applying the specified default and any explicit switch overrides on top.
1107
1108        Opens a temporary session solely to retrieve the engine's default policy via
1109        GW2GetPolicySettings, resolves the Editor policy against those defaults, then
1110        closes the session. No file processing is performed.
1111
1112        Args:
1113            default (Optional[str]): Default None. The value to apply to all content switches,
1114                overriding the engine defaults. (allow, disallow, sanitise). If None, the
1115                engine's own default values are returned unmodified.
1116            config (dict): A dictionary of explicit switch overrides. Keys are config element
1117                names (e.g. "pdfConfig"), values are dicts of switch name to switch value.
1118                Attribute overrides use the "@" prefix (e.g. "@libVersion").
1119
1120        Returns:
1121            policy (glasswall.content_management.policies.policy.Policy): A fully resolved
1122                Policy object. Calling str() or print() on it returns the XML representation.
1123
1124        Example:
1125            >>> print(editor.create_config())
1126            >>> print(editor.create_config(default="sanitise"))
1127            >>> print(editor.create_config(default="allow", config={"pdfConfig": {"metadata": "sanitise"}}))
1128        """
1129        editor_policy = glasswall.content_management.policies.Editor(default=default, config=config)
1130        with self.new_session() as session:
1131            return self._resolve_editor_policy(session, editor_policy)
1132
1133    def protect_and_analyse_directory(
1134        self,
1135        input_directory: str,
1136        output_directory: Optional[str] = None,
1137        analysis_directory: Optional[str] = None,
1138        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1139        raise_unsupported: bool = True,
1140    ):
1141        """ Recursively processes all files in a directory using protect and analyse mode with the given content management policy.
1142        Outputs are written to output_directory and analysis_directory maintaining the same structure as input_directory.
1143
1144        Args:
1145            input_directory (str): The input directory containing files to process.
1146            output_directory (Optional[str]): The output directory for protected files.
1147            analysis_directory (Optional[str]): The output directory for XML analysis reports.
1148            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply.
1149            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1150
1151        Returns:
1152            result_dict (dict): A dictionary mapping relative file paths to tuples of (protected_file_bytes, analysis_report_bytes).
1153        """
1154        result_dict = {}
1155        for input_file in utils.list_file_paths(input_directory):
1156            relative_path = os.path.relpath(input_file, input_directory)
1157            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
1158            output_analysis_report = None if analysis_directory is None else os.path.join(os.path.abspath(analysis_directory), relative_path + ".xml")
1159
1160            protected_file_bytes, analysis_report_bytes = self.protect_and_analyse_file(
1161                input_file=input_file,
1162                output_file=output_file,
1163                output_analysis_report=output_analysis_report,
1164                content_management_policy=content_management_policy,
1165                raise_unsupported=raise_unsupported,
1166            )
1167
1168            result_dict[relative_path] = (protected_file_bytes, analysis_report_bytes)
1169
1170        return result_dict
1171
1172    def _GW2RegisterExportFile(self, session: int, output_file: str):
1173        """ Register an export output file for the given session.
1174
1175        Args:
1176            session (int): The session integer.
1177            output_file (str): The export output file path.
1178
1179        Returns:
1180            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.
1181        """
1182        # API function declaration
1183        self.library.GW2RegisterExportFile.argtypes = [
1184            ct.c_size_t,  # Session_Handle session
1185            ct.c_char_p  # const char * exportFilePath
1186        ]
1187
1188        # Variable initialisation
1189        gw_return_object = glasswall.GwReturnObj()
1190        gw_return_object.session = ct.c_size_t(session)
1191        gw_return_object.output_file = ct.c_char_p(output_file.encode("utf-8"))
1192
1193        # API Call
1194        gw_return_object.status = self.library.GW2RegisterExportFile(
1195            gw_return_object.session,
1196            gw_return_object.output_file
1197        )
1198
1199        return gw_return_object
1200
1201    def _GW2RegisterExportMemory(self, session: int):
1202        """ Register an export output file in memory for the given session.
1203
1204        Args:
1205            session (int): The session integer.
1206
1207        Returns:
1208            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
1209        """
1210        # API function declaration
1211        self.library.GW2RegisterExportMemory.argtypes = [
1212            ct.c_size_t,  # Session_Handle session
1213            ct.POINTER(ct.c_void_p),  # char ** exportFileBuffer
1214            ct.POINTER(ct.c_size_t)  # size_t * exportLength
1215        ]
1216
1217        # Variable initialisation
1218        gw_return_object = glasswall.GwReturnObj()
1219        gw_return_object.session = ct.c_size_t(session)
1220        gw_return_object.buffer = ct.c_void_p()
1221        gw_return_object.buffer_length = ct.c_size_t()
1222
1223        # API call
1224        gw_return_object.status = self.library.GW2RegisterExportMemory(
1225            gw_return_object.session,
1226            ct.byref(gw_return_object.buffer),
1227            ct.byref(gw_return_object.buffer_length)
1228        )
1229
1230        return gw_return_object
1231
1232    def register_export(self, session: int, output_file: Optional[str] = None):
1233        """ Registers a file to be exported for the given session. The export file will be created during the session's run_session call.
1234
1235        Args:
1236            session (int): The session integer.
1237            output_file (Optional[str]): Default None. The file path where the export will be written. None exports the file in memory.
1238
1239        Returns:
1240            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attribute 'status' indicating the result of the function call and 'session', the session integer. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size.
1241        """
1242        if not isinstance(output_file, (type(None), str)):
1243            raise TypeError(output_file)
1244
1245        if isinstance(output_file, str):
1246            output_file = os.path.abspath(output_file)
1247
1248            result = self._GW2RegisterExportFile(session, output_file)
1249
1250        elif isinstance(output_file, type(None)):
1251            result = self._GW2RegisterExportMemory(session)
1252
1253        if result.status not in successes.success_codes:
1254            log.error(format_object(result))
1255            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
1256        else:
1257            log.debug(format_object(result))
1258
1259        return result
1260
1261    def export_file(
1262        self,
1263        input_file: Union[str, bytes, bytearray, io.BytesIO],
1264        output_file: Optional[str] = None,
1265        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1266        raise_unsupported: bool = True,
1267    ):
1268        """ Export a file, returning the .zip file bytes. The .zip file is written to output_file if it is provided.
1269
1270        Args:
1271            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
1272            output_file (Optional[str]): The output file path where the .zip file will be written.
1273            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1274            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1275
1276        Returns:
1277            file_bytes (bytes): The exported .zip file.
1278        """
1279        # Validate arg types
1280        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1281            raise TypeError(input_file)
1282        if not isinstance(output_file, (type(None), str)):
1283            raise TypeError(output_file)
1284        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1285            raise TypeError(content_management_policy)
1286        if not isinstance(raise_unsupported, bool):
1287            raise TypeError(raise_unsupported)
1288
1289        # Convert string path arguments to absolute paths
1290        if isinstance(input_file, str):
1291            if not os.path.isfile(input_file):
1292                raise FileNotFoundError(input_file)
1293            input_file = os.path.abspath(input_file)
1294        if isinstance(output_file, str):
1295            output_file = os.path.abspath(output_file)
1296            # make directories that do not exist
1297            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1298        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1299            content_management_policy = os.path.abspath(content_management_policy)
1300
1301        # Convert memory inputs to bytes
1302        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1303            input_file = utils.as_bytes(input_file)
1304
1305        with utils.CwdHandler(self.library_path):
1306            with self.new_session() as session:
1307                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1308                register_input = self.register_input(session, input_file)
1309                register_export = self.register_export(session, output_file)
1310                status = self.run_session(session)
1311
1312                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1313                if status not in successes.success_codes:
1314                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
1315                    if raise_unsupported:
1316                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1317                    else:
1318                        file_bytes = None
1319                else:
1320                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
1321                    # Get file bytes
1322                    if isinstance(output_file, str):
1323                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1324                        if not os.path.isfile(output_file):
1325                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1326                            file_bytes = None
1327                        else:
1328                            with open(output_file, "rb") as f:
1329                                file_bytes = f.read()
1330                    else:
1331                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1332                        file_bytes = utils.buffer_to_bytes(
1333                            register_export.buffer,
1334                            register_export.buffer_length
1335                        )
1336
1337                # Ensure memory allocated is not garbage collected
1338                content_management_policy, register_input, register_export
1339
1340                return file_bytes
1341
1342    def export_directory(
1343        self,
1344        input_directory: str,
1345        output_directory: Optional[str] = None,
1346        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1347        raise_unsupported: bool = True
1348    ):
1349        """ Exports all files in a directory and its subdirectories. The export files are written to output_directory maintaining the same directory structure as input_directory.
1350
1351        Args:
1352            input_directory (str): The input directory containing files to export.
1353            output_directory (Optional[str]): The output directory where the export files will be written, or None to not write files.
1354            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply.
1355            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1356
1357        Returns:
1358            export_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.
1359        """
1360        export_files_dict = {}
1361        # Call export_file on each file in input_directory to output_directory
1362        for input_file in utils.list_file_paths(input_directory):
1363            relative_path = os.path.relpath(input_file, input_directory) + ".zip"
1364            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
1365
1366            export_bytes = self.export_file(
1367                input_file=input_file,
1368                output_file=output_file,
1369                raise_unsupported=raise_unsupported,
1370                content_management_policy=content_management_policy,
1371            )
1372
1373            export_files_dict[relative_path] = export_bytes
1374
1375        return export_files_dict
1376
1377    def export_and_analyse_file(
1378        self,
1379        input_file: Union[str, bytes, bytearray, io.BytesIO],
1380        output_file: Optional[str] = None,
1381        output_analysis_report: Optional[str] = None,
1382        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1383        raise_unsupported: bool = True,
1384    ):
1385        """ Exports and analyses a file in a single session, returning both exported .zip bytes and analysis report bytes.
1386
1387        Args:
1388            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
1389            output_file (Optional[str]): The output file path where the .zip export will be written.
1390            output_analysis_report (Optional[str]): The output file path where the XML analysis report will be written.
1391            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1392            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1393
1394        Returns:
1395            Tuple[Optional[bytes], Optional[bytes]]: A tuple of (export_file_bytes, analysis_report_bytes).
1396        """
1397        # Validate arg types
1398        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1399            raise TypeError(f"input_file expected to be of type: str, bytes, bytearray, io.BytesIO. Got: {type(input_file).__name__}")
1400        if not isinstance(output_file, (type(None), str)):
1401            raise TypeError(f"output_file expected to be of type: None, str. Got: {type(output_file).__name__}")
1402        if not isinstance(output_analysis_report, (type(None), str)):
1403            raise TypeError(f"output_analysis_report expected to be of type: None, str. Got: {type(output_analysis_report).__name__}")
1404        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1405            raise TypeError(f"content_management_policy expected to be of type: None, str, bytes, bytearray, io.BytesIO, Policy. Got: {type(content_management_policy).__name__}")
1406        if not isinstance(raise_unsupported, bool):
1407            raise TypeError(f"raise_unsupported expected to be of type: bool. Got: {type(raise_unsupported).__name__}")
1408
1409        # Convert string path arguments to absolute paths
1410        if isinstance(input_file, str):
1411            if not os.path.isfile(input_file):
1412                raise FileNotFoundError(input_file)
1413            input_file = os.path.abspath(input_file)
1414        if isinstance(output_file, str):
1415            output_file = os.path.abspath(output_file)
1416            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1417        if isinstance(output_analysis_report, str):
1418            output_analysis_report = os.path.abspath(output_analysis_report)
1419            os.makedirs(os.path.dirname(output_analysis_report), exist_ok=True)
1420        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1421            content_management_policy = os.path.abspath(content_management_policy)
1422
1423        # Convert memory inputs to bytes
1424        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1425            input_file = utils.as_bytes(input_file)
1426
1427        with utils.CwdHandler(self.library_path):
1428            with self.new_session() as session:
1429                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1430                register_input = self.register_input(session, input_file)
1431                register_export = self.register_export(session, output_file)
1432                register_analysis = self.register_analysis(session, output_analysis_report)
1433
1434                status = self.run_session(session)
1435
1436                export_file_bytes = None
1437                analysis_report_bytes = None
1438
1439                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1440                if status not in successes.success_codes:
1441                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1442                    if raise_unsupported:
1443                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1444
1445                # Get analysis report file bytes, even on processing failure
1446                if isinstance(output_analysis_report, str):
1447                    # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1448                    if not os.path.isfile(output_analysis_report):
1449                        log.error(f"Editor returned success code: {status} but no output file was found: {output_analysis_report}")
1450                    else:
1451                        with open(output_analysis_report, "rb") as f:
1452                            analysis_report_bytes = f.read()
1453                else:
1454                    # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1455                    if register_analysis.buffer and register_analysis.buffer_length:
1456                        analysis_report_bytes = utils.buffer_to_bytes(
1457                            register_analysis.buffer,
1458                            register_analysis.buffer_length
1459                        )
1460
1461                # On success, get export file bytes
1462                if status in successes.success_codes:
1463                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1464                    # Get export file bytes
1465                    if isinstance(output_file, str):
1466                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1467                        if not os.path.isfile(output_file):
1468                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1469                        else:
1470                            with open(output_file, "rb") as f:
1471                                export_file_bytes = f.read()
1472                    else:
1473                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1474                        export_file_bytes = utils.buffer_to_bytes(
1475                            register_export.buffer,
1476                            register_export.buffer_length
1477                        )
1478
1479                # Ensure memory allocated is not garbage collected
1480                content_management_policy, register_input, register_export, register_analysis
1481
1482                return export_file_bytes, analysis_report_bytes
1483
1484    def export_and_analyse_directory(
1485        self,
1486        input_directory: str,
1487        output_directory: Optional[str] = None,
1488        analysis_directory: Optional[str] = None,
1489        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1490        raise_unsupported: bool = True,
1491    ):
1492        """ Recursively processes all files in a directory using export and analyse mode with the given content management policy.
1493        Outputs are written to output_directory and analysis_directory maintaining the same structure as input_directory.
1494
1495        Args:
1496            input_directory (str): The input directory containing files to process.
1497            output_directory (Optional[str]): The output directory for exported .zip files.
1498            analysis_directory (Optional[str]): The output directory for XML analysis reports.
1499            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply.
1500            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1501
1502        Returns:
1503            result_dict (dict): A dictionary mapping relative file paths to tuples of (export_file_bytes, analysis_report_bytes).
1504        """
1505        result_dict = {}
1506
1507        for input_file in utils.list_file_paths(input_directory):
1508            relative_path = os.path.relpath(input_file, input_directory)
1509            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path + ".zip")
1510            output_analysis_report = None if analysis_directory is None else os.path.join(os.path.abspath(analysis_directory), relative_path + ".xml")
1511
1512            export_file_bytes, analysis_report_bytes = self.export_and_analyse_file(
1513                input_file=input_file,
1514                output_file=output_file,
1515                output_analysis_report=output_analysis_report,
1516                content_management_policy=content_management_policy,
1517                raise_unsupported=raise_unsupported,
1518            )
1519
1520            result_dict[relative_path] = (export_file_bytes, analysis_report_bytes)
1521
1522        return result_dict
1523
1524    def _GW2RegisterImportFile(self, session: int, input_file: str):
1525        """ Register an import input file for the given session.
1526
1527        Args:
1528            session (int): The session integer.
1529            input_file (str): The input import file path.
1530
1531        Returns:
1532            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.
1533        """
1534        # API function declaration
1535        self.library.GW2RegisterImportFile.argtypes = [
1536            ct.c_size_t,  # Session_Handle session
1537            ct.c_char_p  # const char * importFilePath
1538        ]
1539
1540        # Variable initialisation
1541        gw_return_object = glasswall.GwReturnObj()
1542        gw_return_object.session = ct.c_size_t(session)
1543        gw_return_object.input_file = ct.c_char_p(input_file.encode("utf-8"))
1544
1545        # API Call
1546        gw_return_object.status = self.library.GW2RegisterImportFile(
1547            gw_return_object.session,
1548            gw_return_object.input_file
1549        )
1550
1551        return gw_return_object
1552
1553    def _GW2RegisterImportMemory(self, session: int, input_file: bytes):
1554        """ Register an import input file in memory for the given session.
1555
1556        Args:
1557            session (int): The session integer.
1558            input_file (str): The input import file in memory.
1559
1560        Returns:
1561            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
1562        """
1563        # API function declaration
1564        self.library.GW2RegisterImportMemory.argtypes = [
1565            ct.c_size_t,  # Session_Handle session
1566            ct.c_void_p,  # char * importFileBuffer
1567            ct.c_size_t  # size_t importLength
1568        ]
1569
1570        # Variable initialisation
1571        gw_return_object = glasswall.GwReturnObj()
1572        gw_return_object.session = ct.c_size_t(session)
1573        gw_return_object.buffer = ct.c_char_p(input_file)
1574        gw_return_object.buffer_length = ct.c_size_t(len(input_file))
1575
1576        # API call
1577        gw_return_object.status = self.library.GW2RegisterImportMemory(
1578            gw_return_object.session,
1579            gw_return_object.buffer,
1580            gw_return_object.buffer_length
1581        )
1582
1583        return gw_return_object
1584
1585    def register_import(self, session: int, input_file: Union[str, bytes, bytearray, io.BytesIO]):
1586        """ Registers a .zip file to be imported for the given session. The constructed file will be created during the session's run_session call.
1587
1588        Args:
1589            session (int): The session integer.
1590            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input import file path or bytes.
1591
1592        Returns:
1593            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attribute 'status' indicating the result of the function call. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size.
1594        """
1595        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO,)):
1596            raise TypeError(input_file)
1597
1598        if isinstance(input_file, str):
1599            if not os.path.isfile(input_file):
1600                raise FileNotFoundError(input_file)
1601
1602            input_file = os.path.abspath(input_file)
1603
1604            result = self._GW2RegisterImportFile(session, input_file)
1605
1606        elif isinstance(input_file, (bytes, bytearray, io.BytesIO,)):
1607            # Convert bytearray and io.BytesIO to bytes
1608            input_file = utils.as_bytes(input_file)
1609
1610            result = self._GW2RegisterImportMemory(session, input_file)
1611
1612        if result.status not in successes.success_codes:
1613            log.error(format_object(result))
1614            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
1615        else:
1616            log.debug(format_object(result))
1617
1618        return result
1619
1620    def import_file(
1621        self,
1622        input_file: Union[str, bytes, bytearray, io.BytesIO],
1623        output_file: Optional[str] = None,
1624        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1625        raise_unsupported: bool = True,
1626    ):
1627        """ Import a .zip file, constructs a file from the .zip file and returns the file bytes. The file is written to output_file if it is provided.
1628
1629        Args:
1630            input_file (Union[str, bytes, bytearray, io.BytesIO]): The .zip input file path or bytes.
1631            output_file (Optional[str]): The output file path where the constructed file will be written.
1632            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1633            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1634
1635        Returns:
1636            file_bytes (bytes): The imported file bytes.
1637        """
1638        # Validate arg types
1639        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1640            raise TypeError(input_file)
1641        if not isinstance(output_file, (type(None), str)):
1642            raise TypeError(output_file)
1643        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1644            raise TypeError(content_management_policy)
1645        if not isinstance(raise_unsupported, bool):
1646            raise TypeError(raise_unsupported)
1647
1648        # Convert string path arguments to absolute paths
1649        if isinstance(input_file, str):
1650            if not os.path.isfile(input_file):
1651                raise FileNotFoundError(input_file)
1652            input_file = os.path.abspath(input_file)
1653        if isinstance(output_file, str):
1654            output_file = os.path.abspath(output_file)
1655            # make directories that do not exist
1656            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1657        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1658            content_management_policy = os.path.abspath(content_management_policy)
1659
1660        # Convert memory inputs to bytes
1661        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1662            input_file = utils.as_bytes(input_file)
1663
1664        with utils.CwdHandler(self.library_path):
1665            with self.new_session() as session:
1666                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1667                register_import = self.register_import(session, input_file)
1668                register_output = self.register_output(session, output_file)
1669                status = self.run_session(session)
1670
1671                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1672                if status not in successes.success_codes:
1673                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
1674                    if raise_unsupported:
1675                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1676                    else:
1677                        file_bytes = None
1678                else:
1679                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
1680                    # Get file bytes
1681                    if isinstance(output_file, str):
1682                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1683                        if not os.path.isfile(output_file):
1684                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1685                            file_bytes = None
1686                        else:
1687                            with open(output_file, "rb") as f:
1688                                file_bytes = f.read()
1689                    else:
1690                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1691                        file_bytes = utils.buffer_to_bytes(
1692                            register_output.buffer,
1693                            register_output.buffer_length
1694                        )
1695
1696                # Ensure memory allocated is not garbage collected
1697                content_management_policy, register_import, register_output
1698
1699                return file_bytes
1700
1701    def import_directory(
1702        self,
1703        input_directory: str,
1704        output_directory: Optional[str] = None,
1705        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1706        raise_unsupported: bool = True,
1707    ):
1708        """ Imports all files in a directory and its subdirectories. Files are expected as .zip but this is not forced.
1709        The constructed files are written to output_directory maintaining the same directory structure as input_directory.
1710
1711        Args:
1712            input_directory (str): The input directory containing files to import.
1713            output_directory (Optional[str]): The output directory where the constructed files will be written, or None to not write files.
1714            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply.
1715            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1716
1717        Returns:
1718            import_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.
1719        """
1720        import_files_dict = {}
1721        # Call import_file on each file in input_directory to output_directory
1722        for input_file in utils.list_file_paths(input_directory):
1723            relative_path = os.path.relpath(input_file, input_directory)
1724            # Remove .zip extension from relative_path
1725            if relative_path.endswith(".zip"):
1726                relative_path = os.path.splitext(relative_path)[0]
1727            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
1728
1729            import_bytes = self.import_file(
1730                input_file=input_file,
1731                output_file=output_file,
1732                raise_unsupported=raise_unsupported,
1733                content_management_policy=content_management_policy,
1734            )
1735
1736            import_files_dict[relative_path] = import_bytes
1737
1738        return import_files_dict
1739
1740    def import_and_analyse_file(
1741        self,
1742        input_file: Union[str, bytes, bytearray, io.BytesIO],
1743        output_file: Optional[str] = None,
1744        output_analysis_report: Optional[str] = None,
1745        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1746        raise_unsupported: bool = True,
1747    ):
1748        """ Imports and analyses a file in a single session, returning both imported file bytes and analysis report bytes.
1749
1750        Args:
1751            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
1752            output_file (Optional[str]): The output file path where the imported file will be written.
1753            output_analysis_report (Optional[str]): The output file path where the XML analysis report will be written.
1754            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1755            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1756
1757        Returns:
1758            Tuple[Optional[bytes], Optional[bytes]]: A tuple of (import_file_bytes, analysis_report_bytes).
1759        """
1760        # Validate arg types
1761        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1762            raise TypeError(f"input_file expected to be of type: str, bytes, bytearray, io.BytesIO. Got: {type(input_file).__name__}")
1763        if not isinstance(output_file, (type(None), str)):
1764            raise TypeError(f"output_file expected to be of type: None, str. Got: {type(output_file).__name__}")
1765        if not isinstance(output_analysis_report, (type(None), str)):
1766            raise TypeError(f"output_analysis_report expected to be of type: None, str. Got: {type(output_analysis_report).__name__}")
1767        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1768            raise TypeError(f"content_management_policy expected to be of type: None, str, bytes, bytearray, io.BytesIO, Policy. Got: {type(content_management_policy).__name__}")
1769        if not isinstance(raise_unsupported, bool):
1770            raise TypeError(f"raise_unsupported expected to be of type: bool. Got: {type(raise_unsupported).__name__}")
1771
1772        # Convert string path arguments to absolute paths
1773        if isinstance(input_file, str):
1774            if not os.path.isfile(input_file):
1775                raise FileNotFoundError(input_file)
1776            input_file = os.path.abspath(input_file)
1777        if isinstance(output_file, str):
1778            output_file = os.path.abspath(output_file)
1779            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1780        if isinstance(output_analysis_report, str):
1781            output_analysis_report = os.path.abspath(output_analysis_report)
1782            os.makedirs(os.path.dirname(output_analysis_report), exist_ok=True)
1783        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1784            content_management_policy = os.path.abspath(content_management_policy)
1785
1786        # Convert memory inputs to bytes
1787        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1788            input_file = utils.as_bytes(input_file)
1789
1790        with utils.CwdHandler(self.library_path):
1791            with self.new_session() as session:
1792                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1793                register_import = self.register_import(session, input_file)
1794                register_output = self.register_output(session, output_file)
1795                register_analysis = self.register_analysis(session, output_analysis_report)
1796
1797                status = self.run_session(session)
1798
1799                import_file_bytes = None
1800                analysis_report_bytes = None
1801
1802                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1803                if status not in successes.success_codes:
1804                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1805                    if raise_unsupported:
1806                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1807
1808                # Get analysis report file bytes, even on processing failure
1809                if isinstance(output_analysis_report, str):
1810                    # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1811                    if not os.path.isfile(output_analysis_report):
1812                        log.error(f"Editor returned success code: {status} but no output file was found: {output_analysis_report}")
1813                    else:
1814                        with open(output_analysis_report, "rb") as f:
1815                            analysis_report_bytes = f.read()
1816                else:
1817                    # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1818                    if register_analysis.buffer and register_analysis.buffer_length:
1819                        analysis_report_bytes = utils.buffer_to_bytes(
1820                            register_analysis.buffer,
1821                            register_analysis.buffer_length
1822                        )
1823
1824                # On success, get import file bytes
1825                if status in successes.success_codes:
1826                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1827                    # Get import file bytes
1828                    if isinstance(output_file, str):
1829                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1830                        if not os.path.isfile(output_file):
1831                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1832                        else:
1833                            with open(output_file, "rb") as f:
1834                                import_file_bytes = f.read()
1835                    else:
1836                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1837                        import_file_bytes = utils.buffer_to_bytes(
1838                            register_output.buffer,
1839                            register_output.buffer_length
1840                        )
1841
1842                # Ensure memory allocated is not garbage collected
1843                content_management_policy, register_import, register_output, register_analysis
1844
1845                return import_file_bytes, analysis_report_bytes
1846
1847    def import_and_analyse_directory(
1848        self,
1849        input_directory: str,
1850        output_directory: Optional[str] = None,
1851        analysis_directory: Optional[str] = None,
1852        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1853        raise_unsupported: bool = True,
1854    ):
1855        """ Recursively processes all files in a directory using import and analyse mode with the given content management policy.
1856        Outputs are written to output_directory and analysis_directory maintaining the same structure as input_directory.
1857
1858        Args:
1859            input_directory (str): The input directory containing export .zip files to process.
1860            output_directory (Optional[str]): The output directory for imported files.
1861            analysis_directory (Optional[str]): The output directory for XML analysis reports.
1862            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply.
1863            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1864
1865        Returns:
1866            result_dict (dict): A dictionary mapping relative file paths to tuples of (import_file_bytes, analysis_report_bytes).
1867        """
1868        result_dict = {}
1869
1870        for input_file in utils.list_file_paths(input_directory):
1871            relative_path = os.path.relpath(input_file, input_directory)
1872            # Remove .zip extension from relative_path
1873            if relative_path.endswith(".zip"):
1874                relative_path = os.path.splitext(relative_path)[0]
1875            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
1876            output_analysis_report = None if analysis_directory is None else os.path.join(os.path.abspath(analysis_directory), relative_path + ".xml")
1877
1878            import_file_bytes, analysis_report_bytes = self.import_and_analyse_file(
1879                input_file=input_file,
1880                output_file=output_file,
1881                output_analysis_report=output_analysis_report,
1882                content_management_policy=content_management_policy,
1883                raise_unsupported=raise_unsupported,
1884            )
1885
1886            result_dict[relative_path] = (import_file_bytes, analysis_report_bytes)
1887
1888        return result_dict
1889
1890    def _GW2FileErrorMsg(self, session: int):
1891        """ Retrieve the Glasswall Session Process error message.
1892
1893        Args:
1894            session (int): The session integer.
1895
1896        Returns:
1897            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status', 'error_message'.
1898        """
1899        # API function declaration
1900        self.library.GW2FileErrorMsg.argtypes = [
1901            ct.c_size_t,  # Session_Handle session
1902            ct.POINTER(ct.c_void_p),  # char **errorMsgBuffer
1903            ct.POINTER(ct.c_size_t)  # size_t *errorMsgBufferLength
1904        ]
1905
1906        # Variable initialisation
1907        gw_return_object = glasswall.GwReturnObj()
1908        gw_return_object.session = ct.c_size_t(session)
1909        gw_return_object.buffer = ct.c_void_p()
1910        gw_return_object.buffer_length = ct.c_size_t()
1911
1912        # API call
1913        gw_return_object.status = self.library.GW2FileErrorMsg(
1914            gw_return_object.session,
1915            ct.byref(gw_return_object.buffer),
1916            ct.byref(gw_return_object.buffer_length)
1917        )
1918
1919        # Editor wrote to a buffer, convert it to bytes
1920        error_bytes = utils.buffer_to_bytes(
1921            gw_return_object.buffer,
1922            gw_return_object.buffer_length
1923        )
1924
1925        gw_return_object.error_message = error_bytes.decode()
1926
1927        return gw_return_object
1928
1929    @functools.lru_cache()
1930    def file_error_message(self, session: int) -> str:
1931        """ Retrieve the Glasswall Session Process error message.
1932
1933        Args:
1934            session (int): The session integer.
1935
1936        Returns:
1937            error_message (str): The Glasswall Session Process error message.
1938        """
1939        # Validate arg types
1940        if not isinstance(session, int):
1941            raise TypeError(session)
1942
1943        result = self._GW2FileErrorMsg(session)
1944
1945        if result.status not in successes.success_codes:
1946            log.error(format_object(result))
1947            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
1948        else:
1949            log.debug(format_object(result))
1950
1951        return result.error_message
1952
1953    def GW2GetFileType(self, session: int, file_type_id):
1954        """ Retrieve the file type as a string.
1955
1956        Args:
1957            session (int): The session integer.
1958            file_type_id (int): The file type id.
1959
1960        Returns:
1961            file_type (str): The formal file name for the corresponding file id.
1962        """
1963        # Validate arg types
1964        if not isinstance(session, int):
1965            raise TypeError(session)
1966
1967        # API function declaration
1968        self.library.GW2GetFileType.argtypes = [
1969            ct.c_size_t,
1970            ct.c_size_t,
1971            ct.POINTER(ct.c_size_t),
1972            ct.POINTER(ct.c_void_p)
1973        ]
1974
1975        # Variable initialisation
1976        ct_session = ct.c_size_t(session)
1977        ct_file_type = ct.c_size_t(file_type_id)
1978        ct_buffer_length = ct.c_size_t()
1979        ct_buffer = ct.c_void_p()
1980
1981        # API call
1982        status = self.library.GW2GetFileType(
1983            ct_session,
1984            ct_file_type,
1985            ct.byref(ct_buffer_length),
1986            ct.byref(ct_buffer)
1987        )
1988
1989        if status not in successes.success_codes:
1990            log.error(f"\n\tsession: {session}\n\tstatus: {status}")
1991            raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1992        else:
1993            log.debug(f"\n\tsession: {session}\n\tstatus: {status}")
1994
1995        # Editor wrote to a buffer, convert it to bytes
1996        file_type_bytes = utils.buffer_to_bytes(
1997            ct_buffer,
1998            ct_buffer_length
1999        )
2000
2001        file_type = file_type_bytes.decode()
2002
2003        return file_type
2004
2005    def GW2GetFileTypeID(self, session: int, file_type_str):
2006        """ Retrieve the Glasswall file type id given a file type string.
2007
2008        Args:
2009            session (int): The session integer.
2010            file_type_str (str): The file type as a string.
2011
2012        Returns:
2013            file_type_id (str): The Glasswall file type id for the specified file type.
2014        """
2015        # Validate arg types
2016        if not isinstance(session, int):
2017            raise TypeError(session)
2018
2019        # API function declaration
2020        self.library.GW2GetFileTypeID.argtypes = [
2021            ct.c_size_t,
2022            ct.c_char_p,
2023            ct.POINTER(ct.c_size_t),
2024            ct.POINTER(ct.c_void_p)
2025        ]
2026
2027        # Variable initialisation
2028        ct_session = ct.c_size_t(session)
2029        ct_file_type = ct.c_char_p(file_type_str.encode('utf-8'))
2030        ct_buffer_length = ct.c_size_t()
2031        ct_buffer = ct.c_void_p()
2032
2033        # API call
2034        status = self.library.GW2GetFileTypeID(
2035            ct_session,
2036            ct_file_type,
2037            ct.byref(ct_buffer_length),
2038            ct.byref(ct_buffer)
2039        )
2040
2041        if status not in successes.success_codes:
2042            log.error(f"\n\tsession: {session}\n\tstatus: {status}")
2043            raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
2044        else:
2045            log.debug(f"\n\tsession: {session}\n\tstatus: {status}")
2046
2047        # Editor wrote to a buffer, convert it to bytes
2048        file_type_bytes = utils.buffer_to_bytes(
2049            ct_buffer,
2050            ct_buffer_length
2051        )
2052
2053        file_type_id = file_type_bytes.decode()
2054
2055        return file_type_id
2056
2057    def get_file_type_info(self, file_type: Union[str, int]):
2058        """ Retrieve information about a file type based on its identifier.
2059
2060        Args:
2061            file_type (Union[str, int]): The file type identifier. This can be either a string representing a file
2062            extension (e.g. 'bmp') or an integer corresponding to a file type (e.g. 29).
2063
2064        Returns:
2065            - file_type_info (Union[int, str]): Depending on the input 'file_type':
2066                - If `file_type` is a string (e.g. 'bmp'):
2067                    - If the file type is recognised, returns an integer corresponding to that file type.
2068                    - If the file type is not recognised, returns 0.
2069                - If `file_type` is an integer (e.g. 29):
2070                    - If the integer corresponds to a recognised file type, returns a more detailed string description
2071                        of the file type (e.g. 'BMP Image').
2072                    - If the integer does not match any recognised file type, returns an empty string.
2073        """
2074        # Validate arg types
2075        if not isinstance(file_type, (str, int)):
2076            raise TypeError(file_type)
2077
2078        with utils.CwdHandler(self.library_path):
2079            with self.new_session() as session:
2080
2081                if isinstance(file_type, int):
2082                    file_type_info = self.GW2GetFileType(session, file_type)
2083                if isinstance(file_type, str):
2084                    file_type_info = self.GW2GetFileTypeID(session, file_type)
2085
2086                return file_type_info
2087
2088    @utils.deprecated_function(replacement_function=get_file_type_info)
2089    def get_file_info(self, *args, **kwargs):
2090        """ Deprecated in 1.0.6. Use get_file_type_info. """
2091        pass
2092
2093    def _GW2RegisterReportFile(self, session: int, output_file: str):
2094        """ Register an output report file path for the given session.
2095
2096        Args:
2097            session (int): The session integer.
2098            output_file (str): The file path of the output report file.
2099
2100        Returns:
2101            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.
2102        """
2103        # API function declaration
2104        self.library.GW2RegisterReportFile.argtypes = [
2105            ct.c_size_t,  # Session_Handle session
2106            ct.c_char_p,  # const char * reportFilePathName
2107        ]
2108
2109        # Variable initialisation
2110        gw_return_object = glasswall.GwReturnObj()
2111        gw_return_object.session = ct.c_size_t(session)
2112        gw_return_object.output_file = ct.c_char_p(output_file.encode("utf-8"))
2113
2114        # API call
2115        gw_return_object.status = self.library.GW2RegisterReportFile(
2116            gw_return_object.session,
2117            gw_return_object.output_file
2118        )
2119
2120        return gw_return_object
2121
2122    def register_report_file(self, session: int, output_file: str):
2123        """ Register the report file path for the given session.
2124
2125        Args:
2126            session (int): The session integer.
2127            output_file (str): The file path of the report file.
2128
2129        Returns:
2130            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.
2131        """
2132        # Validate arg types
2133        if not isinstance(session, int):
2134            raise TypeError(session)
2135        if not isinstance(output_file, (type(None), str)):
2136            raise TypeError(output_file)
2137
2138        result = self._GW2RegisterReportFile(session, output_file)
2139
2140        if result.status not in successes.success_codes:
2141            log.error(format_object(result))
2142            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2143        else:
2144            log.debug(format_object(result))
2145
2146        return result
2147
2148    def _GW2GetIdInfo(self, session: int, issue_id: int):
2149        """ Retrieves the group description for the given Issue ID. e.g. issue_id 96 returns "Document Processing Instances"
2150
2151        Args:
2152            session (int): The session integer.
2153            issue_id (int): The issue id.
2154            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
2155
2156        Returns:
2157            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'issue_id', 'buffer_length', 'buffer', 'status', 'id_info'.
2158        """
2159        # API function declaration
2160        self.library.GW2GetIdInfo.argtypes = [
2161            ct.c_size_t,  # Session_Handle session
2162            ct.c_size_t,  # size_t issueId
2163            ct.POINTER(ct.c_size_t),  # size_t * bufferLength
2164            ct.POINTER(ct.c_void_p)  # char ** outputBuffer
2165        ]
2166
2167        # Variable initialisation
2168        gw_return_object = glasswall.GwReturnObj()
2169        gw_return_object.session = ct.c_size_t(session)
2170        gw_return_object.issue_id = ct.c_size_t(issue_id)
2171        gw_return_object.buffer_length = ct.c_size_t()
2172        gw_return_object.buffer = ct.c_void_p()
2173
2174        # API call
2175        gw_return_object.status = self.library.GW2GetIdInfo(
2176            gw_return_object.session,
2177            gw_return_object.issue_id,
2178            ct.byref(gw_return_object.buffer_length),
2179            ct.byref(gw_return_object.buffer)
2180        )
2181
2182        # Editor wrote to a buffer, convert it to bytes
2183        id_info_bytes = utils.buffer_to_bytes(
2184            gw_return_object.buffer,
2185            gw_return_object.buffer_length
2186        )
2187
2188        gw_return_object.id_info = id_info_bytes.decode()
2189
2190        return gw_return_object
2191
2192    def get_id_info(self, issue_id: int, raise_unsupported: bool = True):
2193        """ Retrieves the group description for the given Issue ID. e.g. issue_id 96 returns "Document Processing Instances"
2194
2195        Args:
2196            issue_id (int): The issue id.
2197            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
2198
2199        Returns:
2200            id_info (str): The group description for the given Issue ID.
2201        """
2202        # Validate arg types
2203        if not isinstance(issue_id, int):
2204            raise TypeError(issue_id)
2205
2206        with utils.CwdHandler(self.library_path):
2207            with self.new_session() as session:
2208                result = self._GW2GetIdInfo(session, issue_id)
2209
2210                if result.status not in successes.success_codes:
2211                    log.error(format_object(result))
2212                    if raise_unsupported:
2213                        raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2214                else:
2215                    log.debug(format_object(result))
2216
2217                return result.id_info
2218
2219    def _GW2GetAllIdInfo(self, session: int):
2220        """ Retrieves the XML containing all the Issue ID ranges with their group descriptions
2221
2222        Args:
2223            session (int): The session integer.
2224
2225        Returns:
2226            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'analysis_format', 'status', 'all_id_info'.
2227        """
2228
2229        # API function declaration
2230        self.library.GW2GetAllIdInfo.argtypes = [
2231            ct.c_size_t,  # Session_Handle session
2232            ct.POINTER(ct.c_size_t),  # size_t * bufferLength
2233            ct.POINTER(ct.c_void_p)  # char ** outputBuffer
2234        ]
2235
2236        # Variable initialisation
2237        # The extracted issue Id information is stored in the analysis report, register an analysis session.
2238        gw_return_object = self._GW2RegisterAnalysisMemory(session)
2239
2240        # API call
2241        gw_return_object.status = self.library.GW2GetAllIdInfo(
2242            gw_return_object.session,
2243            ct.byref(gw_return_object.buffer_length),
2244            ct.byref(gw_return_object.buffer)
2245        )
2246
2247        # Editor wrote to a buffer, convert it to bytes
2248        all_id_info_bytes = utils.buffer_to_bytes(
2249            gw_return_object.buffer,
2250            gw_return_object.buffer_length
2251        )
2252
2253        gw_return_object.all_id_info = all_id_info_bytes.decode()
2254
2255        return gw_return_object
2256
2257    def get_all_id_info(self, output_file: Optional[str] = None, raise_unsupported: bool = True) -> str:
2258        """ Retrieves the XML containing all the Issue ID ranges with their group descriptions
2259
2260        Args:
2261            output_file (Optional[str]): The output file path where the analysis file will be written.
2262            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
2263
2264        Returns:
2265            all_id_info (str): A string XML analysis report containing all id info.
2266        """
2267        # Validate arg types
2268        if not isinstance(output_file, (type(None), str)):
2269            raise TypeError(output_file)
2270        if isinstance(output_file, str):
2271            output_file = os.path.abspath(output_file)
2272
2273        with utils.CwdHandler(self.library_path):
2274            with self.new_session() as session:
2275                result = self._GW2GetAllIdInfo(session)
2276
2277                if result.status not in successes.success_codes:
2278                    log.error(format_object(result))
2279                    if raise_unsupported:
2280                        raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2281                else:
2282                    log.debug(format_object(result))
2283
2284                if isinstance(output_file, str):
2285                    # GW2GetAllIdInfo is memory only, write to file
2286                    # make directories that do not exist
2287                    os.makedirs(os.path.dirname(output_file), exist_ok=True)
2288                    with open(output_file, "w") as f:
2289                        f.write(result.all_id_info)
2290
2291                return result.all_id_info
2292
2293    def _GW2FileSessionStatus(self, session: int):
2294        """ Retrieves the Glasswall Session Status. Also gives a high level indication of the processing that was carried out on the last document processed by the library
2295
2296        Args:
2297            session (int): The session integer.
2298            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
2299
2300        Returns:
2301            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'session_status', 'buffer', 'buffer_length', 'status', 'message'.
2302        """
2303        # API function declaration
2304        self.library.GW2FileSessionStatus.argtypes = [
2305            ct.c_size_t,  # Session_Handle session
2306            ct.POINTER(ct.c_int),  # int *glasswallSessionStatus
2307            ct.POINTER(ct.c_void_p),  # char **statusMsgBuffer
2308            ct.POINTER(ct.c_size_t)  # size_t *statusbufferLength
2309        ]
2310
2311        # Variable initialisation
2312        gw_return_object = glasswall.GwReturnObj()
2313        gw_return_object.session = ct.c_size_t(session)
2314        gw_return_object.session_status = ct.c_int()
2315        gw_return_object.buffer = ct.c_void_p()
2316        gw_return_object.buffer_length = ct.c_size_t()
2317
2318        # API call
2319        gw_return_object.status = self.library.GW2FileSessionStatus(
2320            gw_return_object.session,
2321            ct.byref(gw_return_object.session_status),
2322            ct.byref(gw_return_object.buffer),
2323            ct.byref(gw_return_object.buffer_length)
2324        )
2325
2326        # Convert session_status to int
2327        gw_return_object.session_status = gw_return_object.session_status.value
2328
2329        # Editor wrote to a buffer, convert it to bytes
2330        message_bytes = utils.buffer_to_bytes(
2331            gw_return_object.buffer,
2332            gw_return_object.buffer_length
2333        )
2334        gw_return_object.message = message_bytes.decode()
2335
2336        return gw_return_object
2337
2338    def file_session_status_message(self, session: int, raise_unsupported: bool = True) -> str:
2339        """ Retrieves the Glasswall session status message. Gives a high level indication of the processing that was carried out.
2340
2341        Args:
2342            session (int): The session integer.
2343            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
2344
2345        Returns:
2346            result.message (str):The file session status message.
2347        """
2348        # Validate arg types
2349        if not isinstance(session, int):
2350            raise TypeError(session)
2351
2352        result = self._GW2FileSessionStatus(session)
2353
2354        if result.status not in successes.success_codes:
2355            log.error(format_object(result))
2356            if raise_unsupported:
2357                raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2358        else:
2359            log.debug(format_object(result))
2360
2361        return result.message
2362
2363    def _GW2LicenceDetails(self, session: int):
2364        """ Returns a human readable string containing licence details.
2365
2366        Args:
2367            session (int): The session integer.
2368
2369        Returns:
2370            licence_details (str): A human readable string representing the relevant information contained in the licence.
2371        """
2372        # API function declaration
2373        self.library.GW2LicenceDetails.argtypes = [ct.c_size_t]
2374        self.library.GW2LicenceDetails.restype = ct.c_char_p
2375
2376        # Variable initialisation
2377        ct_session = ct.c_size_t(session)
2378
2379        # API call
2380        licence_details = self.library.GW2LicenceDetails(ct_session)
2381
2382        # Convert to Python string
2383        licence_details = ct.string_at(licence_details).decode()
2384
2385        return licence_details
2386
2387    def licence_details(self):
2388        """ Returns a string containing details of the licence.
2389
2390        Returns:
2391            result (str): A string containing details of the licence.
2392        """
2393        with self.new_session() as session:
2394            result = self._GW2LicenceDetails(session)
2395
2396            log.debug(f"\n\tsession: {session}\n\tGW2LicenceDetails: {result}")
2397
2398        return result
2399
2400    def _GW2RegisterExportTextDumpMemory(self, session: int):
2401        """ Registers an export text dump to be written in memory.
2402
2403        Args:
2404            session (int): The session integer.
2405
2406        Returns:
2407            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
2408        """
2409        # API function declaration
2410        self.library.GW2RegisterExportTextDumpMemory.argtypes = [
2411            ct.c_size_t,  # Session_Handle session
2412            ct.POINTER(ct.c_void_p),  # char ** exportTextDumpFileBuffer
2413            ct.POINTER(ct.c_size_t)  # size_t * exportTextDumpLength
2414        ]
2415
2416        # Variable initialisation
2417        gw_return_object = glasswall.GwReturnObj()
2418        gw_return_object.session = ct.c_size_t(session)
2419        gw_return_object.buffer = ct.c_void_p()
2420        gw_return_object.buffer_length = ct.c_size_t()
2421
2422        # API call
2423        gw_return_object.status = self.library.GW2RegisterExportTextDumpMemory(
2424            gw_return_object.session,
2425            ct.byref(gw_return_object.buffer),
2426            ct.byref(gw_return_object.buffer_length)
2427        )
2428
2429        return gw_return_object
2430
2431    def _GW2RegisterExportTextDumpFile(self, session: int, output_file: str):
2432        """ Registers an export text dump to be written to file.
2433
2434        Args:
2435            session (int): The session integer.
2436            output_file (str): The file path of the text dump file.
2437
2438        Returns:
2439            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.
2440        """
2441        # API function declaration
2442        self.library.GW2RegisterExportTextDumpFile.argtypes = [
2443            ct.c_size_t,  # Session_Handle session
2444            ct.c_char_p  # const char * textDumpFilePathName
2445        ]
2446
2447        # Variable initialisation
2448        gw_return_object = glasswall.GwReturnObj()
2449        gw_return_object.session = ct.c_size_t(session)
2450        gw_return_object.output_file = ct.c_char_p(output_file.encode("utf-8"))
2451
2452        # API call
2453        gw_return_object.status = self.library.GW2RegisterExportTextDumpFile(
2454            gw_return_object.session,
2455            gw_return_object.output_file
2456        )
2457
2458        return gw_return_object
2459
2460    def _GW2RegisterLicenceFile(self, session: int, input_file: str):
2461        """ Registers a "gwkey.lic" licence from file path.
2462
2463        Args:
2464            session (int): The session integer.
2465            input_file (str): The "gwkey.lic" licence input file path.
2466
2467        Returns:
2468            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'status'.
2469        """
2470        # API function declaration
2471        self.library.GW2RegisterLicenceFile.argtypes = [
2472            ct.c_size_t,  # Session_Handle session
2473            ct.c_char_p,  # const char *filename
2474        ]
2475
2476        # Variable initialisation
2477        gw_return_object = glasswall.GwReturnObj()
2478        gw_return_object.session = ct.c_size_t(session)
2479        gw_return_object.input_file = ct.c_char_p(input_file.encode("utf-8"))
2480
2481        # API call
2482        gw_return_object.status = self.library.GW2RegisterLicenceFile(
2483            gw_return_object.session,
2484            gw_return_object.input_file,
2485        )
2486
2487        return gw_return_object
2488
2489    def _GW2RegisterLicenceMemory(self, session: int, input_file: bytes):
2490        """ Registers a "gwkey.lic" licence from memory.
2491
2492        Args:
2493            session (int): The session integer.
2494            input_file (bytes): The "gwkey.lic" licence input file.
2495
2496        Returns:
2497            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
2498        """
2499        # API function declaration
2500        self.library.GW2RegisterLicenceMemory.argtypes = [
2501            ct.c_size_t,  # Session_Handle session
2502            ct.c_char_p,  # const char *filename
2503            ct.c_size_t,  # size_t licenceLength
2504        ]
2505
2506        # Variable initialisation
2507        gw_return_object = glasswall.GwReturnObj()
2508        gw_return_object.session = ct.c_size_t(session)
2509        gw_return_object.buffer = ct.c_char_p(input_file)
2510        gw_return_object.buffer_length = ct.c_size_t(len(input_file))
2511
2512        # API call
2513        gw_return_object.status = self.library.GW2RegisterLicenceMemory(
2514            gw_return_object.session,
2515            gw_return_object.buffer,
2516            gw_return_object.buffer_length
2517        )
2518
2519        return gw_return_object
2520
2521    def register_licence(self, session: int, input_file: Union[str, bytes, bytearray, io.BytesIO]):
2522        """ Registers a "gwkey.lic" licence from file path or memory.
2523
2524        Args:
2525            session (int): The session integer.
2526            input_file (Union[str, bytes, bytearray, io.BytesIO]): The "gwkey.lic" licence. It can be provided as a file path (str), bytes, bytearray, or a BytesIO object.
2527
2528        Returns:
2529            - result (glasswall.GwReturnObj): Depending on the input 'input_file':
2530                - If input_file is a str file path:
2531                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'status'.
2532
2533                - If input_file is a file in memory:
2534                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
2535        """
2536        if isinstance(input_file, str):
2537            if not os.path.isfile(input_file):
2538                raise FileNotFoundError(input_file)
2539
2540            input_file = os.path.abspath(input_file)
2541
2542            result = self._GW2RegisterLicenceFile(session, input_file)
2543
2544        elif isinstance(input_file, (bytes, bytearray, io.BytesIO,)):
2545            # Convert bytearray and io.BytesIO to bytes
2546            input_file = utils.as_bytes(input_file)
2547
2548            result = self._GW2RegisterLicenceMemory(session, input_file)
2549
2550        else:
2551            raise TypeError(input_file)
2552
2553        if result.status not in successes.success_codes:
2554            log.error(format_object(result))
2555            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2556        else:
2557            log.debug(format_object(result))
2558
2559        return result
class Editor(glasswall.libraries.library.Library):
  19class Editor(Library):
  20    """ A high level Python wrapper for Glasswall Editor / Core2. """
  21
  22    def __init__(self, library_path: str, licence: Union[str, bytes, bytearray, io.BytesIO] = None):
  23        """ Initialise the Editor instance.
  24
  25        Args:
  26            library_path (str): The file or directory path to the Editor library.
  27            licence (str, bytes, bytearray, or io.BytesIO, optional): The licence file content or path. This can be:
  28                - A string representing the file path to the licence.
  29                - A `bytes` or `bytearray` object containing the licence data.
  30                - An `io.BytesIO` object for in-memory licence data.
  31                If not specified, it is assumed that the licence file is located in the same directory as the `library_path`.
  32        """
  33        super().__init__(library_path)
  34        self.library = self.load_library(os.path.abspath(library_path))
  35        self.licence = licence
  36
  37        # Validate killswitch has not activated
  38        self.validate_licence()
  39
  40        log.info(f"Loaded Glasswall {self.__class__.__name__} version {self.version()} from {self.library_path}")
  41
  42    def validate_licence(self):
  43        """ Validates the licence of the library by checking the licence details.
  44
  45        Raises:
  46            LicenceExpired: If the licence has expired or could not be validated.
  47        """
  48        licence_details = self.licence_details()
  49
  50        bad_details = [
  51            "Unable to Read Licence Key File",
  52            "Licence File Missing Required Contents",
  53            "Licence Expired",
  54        ]
  55
  56        if any(bad_detail.lower() in licence_details.lower() for bad_detail in bad_details):
  57            # bad_details found in licence_details
  58            log.error(f"{self.__class__.__name__} licence validation failed. Licence details:\n{licence_details}")
  59            raise errors.LicenceExpired(licence_details)
  60        else:
  61            log.debug(f"{self.__class__.__name__} licence validated successfully. Licence details:\n{licence_details}")
  62
  63    def version(self):
  64        """ Returns the Glasswall library version.
  65
  66        Returns:
  67            version (str): The Glasswall library version.
  68        """
  69        # API function declaration
  70        self.library.GW2LibVersion.restype = ct.c_char_p
  71
  72        # API call
  73        version = self.library.GW2LibVersion()
  74
  75        # Convert to Python string
  76        version = ct.string_at(version).decode()
  77
  78        return version
  79
  80    def open_session(self):
  81        """ Open a new Glasswall session.
  82
  83        Returns:
  84            session (int): An incrementing integer repsenting the current session.
  85        """
  86        # API call
  87        session = self.library.GW2OpenSession()
  88
  89        log.debug(f"\n\tsession: {session}")
  90
  91        if self.licence:
  92            # Must register licence on each GW2OpenSession if the licence is not in the same directory as the library
  93            self.register_licence(session, self.licence)
  94
  95        return session
  96
  97    def close_session(self, session: int) -> int:
  98        """ Close the Glasswall session. All resources allocated by the session will be destroyed.
  99
 100        Args:
 101            session (int): The session to close.
 102
 103        Returns:
 104            status (int): The status code of the function call.
 105        """
 106        if not isinstance(session, int):
 107            raise TypeError(session)
 108
 109        # API function declaration
 110        self.library.GW2CloseSession.argtypes = [ct.c_size_t]
 111
 112        # Variable initialisation
 113        ct_session = ct.c_size_t(session)
 114
 115        # API call
 116        status = self.library.GW2CloseSession(ct_session)
 117
 118        if status not in successes.success_codes:
 119            log.error(f"\n\tsession: {session}\n\tstatus: {status}")
 120        else:
 121            log.debug(f"\n\tsession: {session}\n\tstatus: {status}")
 122
 123        return status
 124
 125    @contextmanager
 126    def new_session(self):
 127        """ Context manager. Opens a new session on entry and closes the session on exit. """
 128        try:
 129            session = self.open_session()
 130            yield session
 131        finally:
 132            self.close_session(session)
 133
 134    def run_session(self, session):
 135        """ Runs the Glasswall session and begins processing of a file.
 136
 137        Args:
 138            session (int): The session to run.
 139
 140        Returns:
 141            status (int): The status code of the function call.
 142        """
 143        # API function declaration
 144        self.library.GW2RunSession.argtypes = [ct.c_size_t]
 145
 146        # Variable initialisation
 147        ct_session = ct.c_size_t(session)
 148
 149        # API call
 150        status = self.library.GW2RunSession(ct_session)
 151
 152        if status not in successes.success_codes:
 153            log.error(f"\n\tsession: {session}\n\tstatus: {status}\n\tGW2FileErrorMsg: {self.file_error_message(session)}")
 154        else:
 155            log.debug(f"\n\tsession: {session}\n\tstatus: {status}\n\tGW2FileErrorMsg: {self.file_error_message(session)}")
 156
 157        return status
 158
 159    def determine_file_type(self, input_file: Union[str, bytes, bytearray, io.BytesIO], as_string: bool = False, raise_unsupported: bool = True) -> Union[int, str]:
 160        """ Determine the file type of a given input file, either as an integer identifier or a string.
 161
 162        Args:
 163            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file to analyse. It can be provided as a file path (str), bytes, bytearray, or a BytesIO object.
 164            as_string (bool, optional): Return file type as string, eg: "bmp" instead of: 29. Defaults to False.
 165            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
 166
 167        Returns:
 168            file_type (Union[int, str]): The file type.
 169        """
 170        if isinstance(input_file, str):
 171            if not os.path.isfile(input_file):
 172                raise FileNotFoundError(input_file)
 173
 174            # convert to ct.c_char_p of bytes
 175            ct_input_file = ct.c_char_p(input_file.encode("utf-8"))
 176
 177            # API call
 178            file_type = self.library.GW2DetermineFileTypeFromFile(ct_input_file)
 179
 180        elif isinstance(input_file, (bytes, bytearray, io.BytesIO)):
 181            # convert to bytes
 182            bytes_input_file = utils.as_bytes(input_file)
 183
 184            # ctypes conversion
 185            ct_buffer = ct.c_char_p(bytes_input_file)
 186            ct_buffer_length = ct.c_size_t(len(bytes_input_file))
 187
 188            # API call
 189            file_type = self.library.GW2DetermineFileTypeFromMemory(
 190                ct_buffer,
 191                ct_buffer_length
 192            )
 193
 194        else:
 195            raise TypeError(input_file)
 196
 197        file_type_as_string = dft.file_type_int_to_str(file_type)
 198        input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
 199
 200        if not dft.is_success(file_type):
 201            log.warning(f"\n\tfile_type: {file_type}\n\tfile_type_as_string: {file_type_as_string}\n\tinput_file: {input_file_repr}")
 202            if raise_unsupported:
 203                raise dft.int_class_map.get(file_type, dft.errors.UnknownErrorCode)(file_type)
 204        else:
 205            log.debug(f"\n\tfile_type: {file_type}\n\tfile_type_as_string: {file_type_as_string}\n\tinput_file: {input_file_repr}")
 206
 207        if as_string:
 208            return file_type_as_string
 209
 210        return file_type
 211
 212    def _GW2GetPolicySettings(self, session: int, policy_format: int = 0):
 213        """ Get current policy settings for the given session.
 214
 215        Args:
 216            session (int): The session integer.
 217            policy_format (int): The format of the content management policy. 0=XML.
 218
 219        Returns:
 220            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'policy_format', 'status', 'policy'.
 221        """
 222        # API function declaration
 223        self.library.GW2GetPolicySettings.argtypes = [
 224            ct.c_size_t,  # Session_Handle session
 225            ct.POINTER(ct.c_void_p),  # char ** policiesBuffer
 226            ct.POINTER(ct.c_size_t),  # size_t * policiesLength
 227            ct.c_int,  # Policy_Format format
 228        ]
 229
 230        # Variable initialisation
 231        gw_return_object = glasswall.GwReturnObj()
 232        gw_return_object.session = ct.c_size_t(session)
 233        gw_return_object.buffer = ct.c_void_p()
 234        gw_return_object.buffer_length = ct.c_size_t()
 235        gw_return_object.policy_format = ct.c_int(policy_format)
 236
 237        # API Call
 238        gw_return_object.status = self.library.GW2GetPolicySettings(
 239            gw_return_object.session,
 240            ct.byref(gw_return_object.buffer),
 241            ct.byref(gw_return_object.buffer_length),
 242            gw_return_object.policy_format
 243        )
 244
 245        # Editor wrote to a buffer, convert it to bytes
 246        policy_bytes = utils.buffer_to_bytes(
 247            gw_return_object.buffer,
 248            gw_return_object.buffer_length
 249        )
 250
 251        gw_return_object.policy = policy_bytes.decode()
 252
 253        return gw_return_object
 254
 255    def get_content_management_policy(self, session: int) -> str:
 256        """ Returns the content management configuration for a given session by querying the engine.
 257
 258        Args:
 259            session (int): The session integer.
 260
 261        Returns:
 262            xml_string (str): The XML string of the current content management configuration.
 263        """
 264        gw_return_object = self._GW2GetPolicySettings(session)
 265
 266        if gw_return_object.status not in successes.success_codes:
 267            log.error(
 268                f"GW2GetPolicySettings failed with status: "
 269                f"{gw_return_object.status}"
 270            )
 271            raise errors.error_codes.get(
 272                gw_return_object.status, errors.UnknownErrorCode
 273            )(gw_return_object.status)
 274
 275        log.debug(
 276            f"GW2GetPolicySettings returned policy:\n"
 277            f"{gw_return_object.policy}"
 278        )
 279
 280        return gw_return_object.policy
 281
 282    def _GW2RegisterPoliciesFile(self, session: int, input_file: str, policy_format: int = 0):
 283        """ Registers the policies to be used by Glasswall when processing files.
 284
 285        Args:
 286            session (int): The session integer.
 287            input_file (str): The content management policy input file path.
 288            policy_format (int): The format of the content management policy. 0=XML.
 289
 290        Returns:
 291            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'policy_format', 'status'.
 292        """
 293        # API function declaration
 294        self.library.GW2RegisterPoliciesFile.argtypes = [
 295            ct.c_size_t,  # Session_Handle session
 296            ct.c_char_p,  # const char *filename
 297            ct.c_int,  # Policy_Format format
 298        ]
 299
 300        # Variable initialisation
 301        gw_return_object = glasswall.GwReturnObj()
 302        gw_return_object.session = ct.c_size_t(session)
 303        gw_return_object.input_file = ct.c_char_p(input_file.encode("utf-8"))
 304        gw_return_object.policy_format = ct.c_int(policy_format)
 305
 306        gw_return_object.status = self.library.GW2RegisterPoliciesFile(
 307            gw_return_object.session,
 308            gw_return_object.input_file,
 309            gw_return_object.policy_format
 310        )
 311
 312        return gw_return_object
 313
 314    def _GW2RegisterPoliciesMemory(self, session: int, input_file: bytes, policy_format: int = 0):
 315        """ Registers the policies in memory to be used by Glasswall when processing files.
 316
 317        Args:
 318            session (int): The session integer.
 319            input_file (str): The content management policy input file bytes.
 320            policy_format (int): The format of the content management policy. 0=XML.
 321
 322        Returns:
 323            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'policy_format', 'status'.
 324        """
 325        # API function declaration
 326        self.library.GW2RegisterPoliciesMemory.argtype = [
 327            ct.c_size_t,  # Session_Handle session
 328            ct.c_char_p,  # const char *policies
 329            ct.c_size_t,  # size_t policiesLength
 330            ct.c_int  # Policy_Format format
 331        ]
 332
 333        # Variable initialisation
 334        gw_return_object = glasswall.GwReturnObj()
 335        gw_return_object.session = ct.c_size_t(session)
 336        gw_return_object.buffer = ct.c_char_p(input_file)
 337        gw_return_object.buffer_length = ct.c_size_t(len(input_file))
 338        gw_return_object.policy_format = ct.c_int(policy_format)
 339
 340        # API Call
 341        gw_return_object.status = self.library.GW2RegisterPoliciesMemory(
 342            gw_return_object.session,
 343            gw_return_object.buffer,
 344            gw_return_object.buffer_length,
 345            gw_return_object.policy_format
 346        )
 347
 348        return gw_return_object
 349
 350    def _resolve_editor_policy(
 351        self,
 352        session: int,
 353        editor_policy: "glasswall.content_management.policies.Editor",
 354    ) -> "glasswall.content_management.policies.policy.Policy":
 355        """ Resolves an Editor policy against the engine's default settings.
 356
 357        Queries the engine for its default policy settings, applies the
 358        Editor's default value to all content switches, then applies any
 359        explicit config overrides on top.
 360
 361        Args:
 362            session (int): The session integer.
 363            editor_policy (glasswall.content_management.policies.Editor):
 364                An Editor policy instance with default and config
 365                attributes.
 366
 367        Returns:
 368            policy (glasswall.content_management.policies.policy.Policy):
 369                A fully resolved Policy object.
 370        """
 371        from glasswall.content_management.config_elements.config_element import ConfigElement
 372        from glasswall.content_management.switches.switch import Switch
 373
 374        # Query engine for default policy settings
 375        engine_xml = self.get_content_management_policy(session)
 376        policy = glasswall.content_management.policies.Policy.from_string(engine_xml)
 377
 378        # Standard content switch values used by file-type config elements
 379        # (e.g. pdfConfig, wordConfig). System switches (sysConfig) use
 380        # different vocabularies ("true"/"false", "sisl"/"xml", etc.) and
 381        # must not be overwritten with a content default.
 382        content_switch_values = {"allow", "disallow", "sanitise"}
 383
 384        # Apply default value to content switches only
 385        if editor_policy.default is not None:
 386            for config_element in policy.config_elements:
 387                for switch in config_element.switches:
 388                    # Only set if default is a valid value for this switch
 389                    if switch.restrict_values and editor_policy.default in switch.restrict_values:
 390                        switch.value = editor_policy.default
 391                    elif switch.value in content_switch_values:
 392                        # No restrict_values defined (base Switch from dynamic parsing).
 393                        # Only override if the current value is a standard content switch
 394                        # value, indicating this is a content switch rather than a system switch.
 395                        switch.value = editor_policy.default
 396
 397        # Apply explicit config overrides
 398        for config_element_name, switches in editor_policy.config.items():
 399            # Find existing config element
 400            matching = [c for c in policy.config_elements if c.name == config_element_name]
 401            if matching:
 402                target = matching[0]
 403                for switch_name, switch_value in switches.items():
 404                    if switch_name.startswith("@"):
 405                        target.attributes[switch_name[1:]] = switch_value
 406                    else:
 407                        target.add_switch(Switch(name=switch_name, value=switch_value))
 408            else:
 409                # Create new config element for unknown config sections
 410                new_ce = ConfigElement(name=config_element_name)
 411                for switch_name, switch_value in switches.items():
 412                    if switch_name.startswith("@"):
 413                        new_ce.attributes[switch_name[1:]] = switch_value
 414                    else:
 415                        new_ce.add_switch(Switch(name=switch_name, value=switch_value))
 416                policy.add_config_element(new_ce)
 417
 418        return policy
 419
 420    def set_content_management_policy(self, session: int, input_file: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None, policy_format=0):
 421        """ Sets the content management policy configuration. If input_file is None then the engine's own default settings are applied unmodified.
 422
 423        Args:
 424            session (int): The session integer.
 425            input_file (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (engine defaults). The content management policy to apply.
 426            policy_format (int): The format of the content management policy. 0=XML.
 427
 428        Returns:
 429            - result (glasswall.GwReturnObj): Depending on the input 'input_file':
 430                - If input_file is a str file path:
 431                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'policy_format', 'status'.
 432
 433                - If input_file is a file in memory:
 434                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'policy_format', 'status'.
 435        """
 436        # Validate type
 437        if not isinstance(session, int):
 438            raise TypeError(session)
 439        if not isinstance(input_file, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
 440            raise TypeError(input_file)
 441        if not isinstance(policy_format, int):
 442            raise TypeError(policy_format)
 443
 444        # Handle None: resolve against engine defaults without overriding any switch values
 445        if input_file is None:
 446            input_file = glasswall.content_management.policies.Editor(default=None)
 447
 448        # Handle Editor policy: resolve against engine defaults
 449        if isinstance(input_file, glasswall.content_management.policies.Editor):
 450            input_file = self._resolve_editor_policy(session, input_file)
 451
 452        # Validate xml content is parsable
 453        utils.validate_xml(input_file)
 454
 455        # From file
 456        if isinstance(input_file, str) and os.path.isfile(input_file):
 457            input_file = os.path.abspath(input_file)
 458
 459            result = self._GW2RegisterPoliciesFile(session, input_file)
 460
 461        # From memory
 462        elif isinstance(input_file, (str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
 463            # Convert bytearray, io.BytesIO to bytes
 464            if isinstance(input_file, (bytearray, io.BytesIO)):
 465                input_file = utils.as_bytes(input_file)
 466            # Convert string xml or Policy to bytes
 467            if isinstance(input_file, (str, glasswall.content_management.policies.policy.Policy)):
 468                input_file = input_file.encode("utf-8")
 469
 470            result = self._GW2RegisterPoliciesMemory(session, input_file)
 471
 472        if result.status not in successes.success_codes:
 473            log.error(format_object(result))
 474            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
 475        else:
 476            log.debug(format_object(result))
 477
 478        return result
 479
 480    def _GW2RegisterInputFile(self, session: int, input_file: str):
 481        """ Register an input file for the given session.
 482
 483        Args:
 484            session (int): The session integer.
 485            input_file (str): The input file path.
 486
 487        Returns:
 488            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'status'.
 489        """
 490        # API function declaration
 491        self.library.GW2RegisterInputFile.argtypes = [
 492            ct.c_size_t,  # Session_Handle session
 493            ct.c_char_p  # const char * inputFilePath
 494        ]
 495
 496        # Variable initialisation
 497        gw_return_object = glasswall.GwReturnObj()
 498        gw_return_object.session = ct.c_size_t(session)
 499        gw_return_object.input_file = ct.c_char_p(input_file.encode("utf-8"))
 500        gw_return_object.input_file_path = input_file
 501
 502        # API call
 503        gw_return_object.status = self.library.GW2RegisterInputFile(
 504            gw_return_object.session,
 505            gw_return_object.input_file
 506        )
 507
 508        return gw_return_object
 509
 510    def _GW2RegisterInputMemory(self, session: int, input_file: bytes):
 511        """ Register an input file in memory for the given session.
 512
 513        Args:
 514            session (int): The session integer.
 515            input_file (bytes): The input file in memory.
 516
 517        Returns:
 518            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
 519        """
 520        # API function declaration
 521        self.library.GW2RegisterInputMemory.argtypes = [
 522            ct.c_size_t,  # Session_Handle session
 523            ct.c_char_p,  # const char * inputFileBuffer
 524            ct.c_size_t,  # size_t inputLength
 525        ]
 526
 527        # Variable initialisation
 528        gw_return_object = glasswall.GwReturnObj()
 529        gw_return_object.session = ct.c_size_t(session)
 530        gw_return_object.buffer = ct.c_char_p(input_file)
 531        gw_return_object.buffer_length = ct.c_size_t(len(input_file))
 532
 533        # API call
 534        gw_return_object.status = self.library.GW2RegisterInputMemory(
 535            gw_return_object.session,
 536            gw_return_object.buffer,
 537            gw_return_object.buffer_length
 538        )
 539
 540        return gw_return_object
 541
 542    def register_input(self, session: int, input_file: Union[str, bytes, bytearray, io.BytesIO]):
 543        """ Register an input file or bytes for the given session.
 544
 545        Args:
 546            session (int): The session integer.
 547            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
 548
 549        Returns:
 550            - result (glasswall.GwReturnObj): Depending on the input 'input_file':
 551                - If input_file is a str file path:
 552                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'status'.
 553
 554                - If input_file is a file in memory:
 555                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
 556        """
 557        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO,)):
 558            raise TypeError(input_file)
 559
 560        if isinstance(input_file, str):
 561            if not os.path.isfile(input_file):
 562                raise FileNotFoundError(input_file)
 563
 564            input_file = os.path.abspath(input_file)
 565
 566            result = self._GW2RegisterInputFile(session, input_file)
 567
 568        elif isinstance(input_file, (bytes, bytearray, io.BytesIO,)):
 569            # Convert bytearray and io.BytesIO to bytes
 570            input_file = utils.as_bytes(input_file)
 571
 572            result = self._GW2RegisterInputMemory(session, input_file)
 573
 574        if result.status not in successes.success_codes:
 575            log.error(format_object(result))
 576            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
 577        else:
 578            log.debug(format_object(result))
 579
 580        return result
 581
 582    def _GW2RegisterOutFile(self, session: int, output_file: str):
 583        """ Register an output file for the given session.
 584
 585        Args:
 586            session (int): The session integer.
 587            output_file (str): The output file path.
 588
 589        Returns:
 590            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.
 591        """
 592        # API function declaration
 593        self.library.GW2RegisterOutFile.argtypes = [
 594            ct.c_size_t,  # Session_Handle session
 595            ct.c_char_p  # const char * outputFilePath
 596        ]
 597
 598        # Variable initialisation
 599        gw_return_object = glasswall.GwReturnObj()
 600        gw_return_object.session = ct.c_size_t(session)
 601        gw_return_object.output_file = ct.c_char_p(output_file.encode("utf-8"))
 602
 603        # API call
 604        gw_return_object.status = self.library.GW2RegisterOutFile(
 605            gw_return_object.session,
 606            gw_return_object.output_file
 607        )
 608
 609        return gw_return_object
 610
 611    def _GW2RegisterOutputMemory(self, session: int):
 612        """ Register an output file in memory for the given session.
 613
 614        Args:
 615            session (int): The session integer.
 616
 617        Returns:
 618            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
 619        """
 620        # API function declaration
 621        self.library.GW2RegisterOutputMemory.argtypes = [
 622            ct.c_size_t,  # Session_Handle session
 623            ct.POINTER(ct.c_void_p),  # char ** outputBuffer
 624            ct.POINTER(ct.c_size_t)  # size_t * outputLength
 625        ]
 626
 627        # Variable initialisation
 628        gw_return_object = glasswall.GwReturnObj()
 629        gw_return_object.session = ct.c_size_t(session)
 630        gw_return_object.buffer = ct.c_void_p()
 631        gw_return_object.buffer_length = ct.c_size_t()
 632
 633        # API call
 634        gw_return_object.status = self.library.GW2RegisterOutputMemory(
 635            gw_return_object.session,
 636            ct.byref(gw_return_object.buffer),
 637            ct.byref(gw_return_object.buffer_length)
 638        )
 639
 640        return gw_return_object
 641
 642    def register_output(self, session, output_file: Optional[str] = None):
 643        """ Register an output file for the given session. If output_file is None the file will be returned as 'buffer' and 'buffer_length' attributes.
 644
 645        Args:
 646            session (int): The session integer.
 647            output_file (Optional[str]): If specified, during run session the file will be written to output_file, otherwise the file will be written to the glasswall.GwReturnObj 'buffer' and 'buffer_length' attributes.
 648
 649        Returns:
 650            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attribute 'status' indicating the result of the function call. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size.
 651        """
 652        if not isinstance(output_file, (type(None), str)):
 653            raise TypeError(output_file)
 654
 655        if isinstance(output_file, str):
 656            output_file = os.path.abspath(output_file)
 657
 658            result = self._GW2RegisterOutFile(session, output_file)
 659
 660        elif isinstance(output_file, type(None)):
 661            result = self._GW2RegisterOutputMemory(session)
 662
 663        if result.status not in successes.success_codes:
 664            log.error(format_object(result))
 665            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
 666        else:
 667            log.debug(format_object(result))
 668
 669        return result
 670
 671    def _GW2RegisterAnalysisFile(self, session: int, output_file: str, analysis_format: int = 0):
 672        """ Register an analysis output file for the given session.
 673
 674        Args:
 675            session (int): The session integer.
 676            output_file (str): The analysis output file path.
 677            analysis_format (int): The format of the analysis report. 0=XML, 1=XMLExtended
 678
 679        Returns:
 680            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'analysis_format', 'status'.
 681        """
 682        # API function declaration
 683        self.library.GW2RegisterAnalysisFile.argtypes = [
 684            ct.c_size_t,  # Session_Handle session
 685            ct.c_char_p,  # const char * analysisFilePathName
 686            ct.c_int,  # Analysis_Format format
 687        ]
 688
 689        # Variable initialisation
 690        gw_return_object = glasswall.GwReturnObj()
 691        gw_return_object.session = ct.c_size_t(session)
 692        gw_return_object.output_file = ct.c_char_p(output_file.encode("utf-8"))
 693        gw_return_object.analysis_format = ct.c_int()
 694
 695        # API call
 696        gw_return_object.status = self.library.GW2RegisterAnalysisFile(
 697            gw_return_object.session,
 698            gw_return_object.output_file,
 699            gw_return_object.analysis_format
 700        )
 701
 702        return gw_return_object
 703
 704    def _GW2RegisterAnalysisMemory(self, session: int, analysis_format: int = 0):
 705        """ Register an analysis output file in memory for the given session.
 706
 707        Args:
 708            session (int): The session integer.
 709            analysis_format (int): The format of the analysis report. 0=XML, 1=XMLExtended
 710
 711        Returns:
 712            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'analysis_format', 'status'.
 713        """
 714        # API function declaration
 715        self.library.GW2RegisterAnalysisMemory.argtypes = [
 716            ct.c_size_t,  # Session_Handle session
 717            ct.POINTER(ct.c_void_p),  # char ** analysisFileBuffer
 718            ct.POINTER(ct.c_size_t),  # size_t * analysisoutputLength
 719            ct.c_int  # Analysis_Format format
 720        ]
 721
 722        # Variable initialisation
 723        gw_return_object = glasswall.GwReturnObj()
 724        gw_return_object.session = ct.c_size_t(session)
 725        gw_return_object.buffer = ct.c_void_p()
 726        gw_return_object.buffer_length = ct.c_size_t()
 727        gw_return_object.analysis_format = ct.c_int()
 728
 729        # API call
 730        gw_return_object.status = self.library.GW2RegisterAnalysisMemory(
 731            gw_return_object.session,
 732            ct.byref(gw_return_object.buffer),
 733            ct.byref(gw_return_object.buffer_length),
 734            gw_return_object.analysis_format
 735        )
 736
 737        return gw_return_object
 738
 739    def register_analysis(self, session: int, output_file: Optional[str] = None):
 740        """ Registers an analysis file for the given session. The analysis file will be created during the session's run_session call.
 741
 742        Args:
 743            session (int): The session integer.
 744            output_file (Optional[str]): Default None. The file path where the analysis will be written. None returns the analysis as bytes.
 745
 746        Returns:
 747            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'status', 'session', 'analysis_format'. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size. If output_file is not None (file mode) 'output_file' is included.
 748        """
 749        if not isinstance(output_file, (type(None), str)):
 750            raise TypeError(output_file)
 751
 752        if isinstance(output_file, str):
 753            output_file = os.path.abspath(output_file)
 754
 755            result = self._GW2RegisterAnalysisFile(session, output_file)
 756
 757        elif isinstance(output_file, type(None)):
 758            result = self._GW2RegisterAnalysisMemory(session)
 759
 760        if result.status not in successes.success_codes:
 761            log.error(format_object(result))
 762            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
 763        else:
 764            log.debug(format_object(result))
 765
 766        return result
 767
 768    def protect_file(
 769        self,
 770        input_file: Union[str, bytes, bytearray, io.BytesIO],
 771        output_file: Optional[str] = None,
 772        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
 773        raise_unsupported: bool = True,
 774    ):
 775        """ Protects a file using the current content management configuration, returning the file bytes. The protected file is written to output_file if it is provided.
 776
 777        Args:
 778            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
 779            output_file (Optional[str]): The output file path where the protected file will be written.
 780            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
 781            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
 782
 783        Returns:
 784            file_bytes (bytes): The protected file bytes.
 785        """
 786        # Validate arg types
 787        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
 788            raise TypeError(input_file)
 789        if not isinstance(output_file, (type(None), str)):
 790            raise TypeError(output_file)
 791        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
 792            raise TypeError(content_management_policy)
 793        if not isinstance(raise_unsupported, bool):
 794            raise TypeError(raise_unsupported)
 795
 796        # Convert string path arguments to absolute paths
 797        if isinstance(input_file, str):
 798            if not os.path.isfile(input_file):
 799                raise FileNotFoundError(input_file)
 800            input_file = os.path.abspath(input_file)
 801        if isinstance(output_file, str):
 802            output_file = os.path.abspath(output_file)
 803            # make directories that do not exist
 804            os.makedirs(os.path.dirname(output_file), exist_ok=True)
 805        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
 806            content_management_policy = os.path.abspath(content_management_policy)
 807
 808        # Convert memory inputs to bytes
 809        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
 810            input_file = utils.as_bytes(input_file)
 811
 812        with utils.CwdHandler(self.library_path):
 813            with self.new_session() as session:
 814                content_management_policy = self.set_content_management_policy(session, content_management_policy)
 815                register_input = self.register_input(session, input_file)
 816                register_output = self.register_output(session, output_file=output_file)
 817                status = self.run_session(session)
 818
 819                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
 820                if status not in successes.success_codes:
 821                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
 822                    if raise_unsupported:
 823                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
 824                    else:
 825                        file_bytes = None
 826                else:
 827                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
 828                    # Get file bytes
 829                    if isinstance(output_file, str):
 830                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
 831                        if not os.path.isfile(output_file):
 832                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
 833                            file_bytes = None
 834                        else:
 835                            with open(output_file, "rb") as f:
 836                                file_bytes = f.read()
 837                    else:
 838                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
 839                        file_bytes = utils.buffer_to_bytes(
 840                            register_output.buffer,
 841                            register_output.buffer_length
 842                        )
 843
 844                # Ensure memory allocated is not garbage collected
 845                content_management_policy, register_input, register_output
 846
 847                return file_bytes
 848
 849    def protect_directory(
 850        self,
 851        input_directory: str,
 852        output_directory: Optional[str] = None,
 853        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
 854        raise_unsupported: bool = True,
 855    ):
 856        """ Recursively processes all files in a directory in protect mode using the given content management policy.
 857        The protected files are written to output_directory maintaining the same directory structure as input_directory.
 858
 859        Args:
 860            input_directory (str): The input directory containing files to protect.
 861            output_directory (Optional[str]): The output directory where the protected file will be written, or None to not write files.
 862            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply.
 863            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
 864
 865        Returns:
 866            protected_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.
 867        """
 868        protected_files_dict = {}
 869        # Call protect_file on each file in input_directory to output_directory
 870        for input_file in utils.list_file_paths(input_directory):
 871            relative_path = os.path.relpath(input_file, input_directory)
 872            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
 873
 874            protected_bytes = self.protect_file(
 875                input_file=input_file,
 876                output_file=output_file,
 877                raise_unsupported=raise_unsupported,
 878                content_management_policy=content_management_policy,
 879            )
 880
 881            protected_files_dict[relative_path] = protected_bytes
 882
 883        return protected_files_dict
 884
 885    def analyse_file(
 886        self,
 887        input_file: Union[str, bytes, bytearray, io.BytesIO],
 888        output_file: Optional[str] = None,
 889        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
 890        raise_unsupported: bool = True,
 891    ):
 892        """ Analyses a file, returning the analysis bytes. The analysis is written to output_file if it is provided.
 893
 894        Args:
 895            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
 896            output_file (Optional[str]): The output file path where the analysis file will be written.
 897            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
 898            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
 899
 900        Returns:
 901            file_bytes (bytes): The analysis file bytes.
 902        """
 903        # Validate arg types
 904        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
 905            raise TypeError(input_file)
 906        if not isinstance(output_file, (type(None), str)):
 907            raise TypeError(output_file)
 908        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
 909            raise TypeError(content_management_policy)
 910        if not isinstance(raise_unsupported, bool):
 911            raise TypeError(raise_unsupported)
 912
 913        # Convert string path arguments to absolute paths
 914        if isinstance(input_file, str):
 915            if not os.path.isfile(input_file):
 916                raise FileNotFoundError(input_file)
 917            input_file = os.path.abspath(input_file)
 918        if isinstance(output_file, str):
 919            output_file = os.path.abspath(output_file)
 920            # make directories that do not exist
 921            os.makedirs(os.path.dirname(output_file), exist_ok=True)
 922        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
 923            content_management_policy = os.path.abspath(content_management_policy)
 924
 925        # Convert memory inputs to bytes
 926        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
 927            input_file = utils.as_bytes(input_file)
 928
 929        with utils.CwdHandler(self.library_path):
 930            with self.new_session() as session:
 931                content_management_policy = self.set_content_management_policy(session, content_management_policy)
 932                register_input = self.register_input(session, input_file)
 933                register_analysis = self.register_analysis(session, output_file)
 934                status = self.run_session(session)
 935
 936                file_bytes = None
 937                if isinstance(output_file, str):
 938                    # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
 939                    if os.path.isfile(output_file):
 940                        with open(output_file, "rb") as f:
 941                            file_bytes = f.read()
 942                else:
 943                    # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
 944                    if register_analysis.buffer and register_analysis.buffer_length:
 945                        file_bytes = utils.buffer_to_bytes(
 946                            register_analysis.buffer,
 947                            register_analysis.buffer_length
 948                        )
 949
 950                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
 951                if status not in successes.success_codes:
 952                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
 953                    if raise_unsupported:
 954                        error = errors.error_codes.get(status, errors.UnknownErrorCode)(status)
 955                        error.analysis_bytes = file_bytes
 956                        raise error
 957                else:
 958                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
 959
 960                # Ensure memory allocated is not garbage collected
 961                content_management_policy, register_input, register_analysis
 962
 963                return file_bytes
 964
 965    def analyse_directory(
 966        self,
 967        input_directory: str,
 968        output_directory: Optional[str] = None,
 969        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
 970        raise_unsupported: bool = True,
 971    ):
 972        """ Analyses all files in a directory and its subdirectories. The analysis files are written to output_directory maintaining the same directory structure as input_directory.
 973
 974        Args:
 975            input_directory (str): The input directory containing files to analyse.
 976            output_directory (Optional[str]): The output directory where the analysis files will be written, or None to not write files.
 977            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply.
 978            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
 979
 980        Returns:
 981            analysis_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.
 982        """
 983        analysis_files_dict = {}
 984        # Call analyse_file on each file in input_directory to output_directory
 985        for input_file in utils.list_file_paths(input_directory):
 986            relative_path = os.path.relpath(input_file, input_directory) + ".xml"
 987            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
 988
 989            analysis_bytes = self.analyse_file(
 990                input_file=input_file,
 991                output_file=output_file,
 992                raise_unsupported=raise_unsupported,
 993                content_management_policy=content_management_policy,
 994            )
 995
 996            analysis_files_dict[relative_path] = analysis_bytes
 997
 998        return analysis_files_dict
 999
1000    def protect_and_analyse_file(
1001        self,
1002        input_file: Union[str, bytes, bytearray, io.BytesIO],
1003        output_file: Optional[str] = None,
1004        output_analysis_report: Optional[str] = None,
1005        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1006        raise_unsupported: bool = True,
1007    ):
1008        """ Protects and analyses a file in a single session, returning both protected file bytes and analysis report bytes.
1009
1010        Args:
1011            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
1012            output_file (Optional[str]): The output file path where the protected file will be written.
1013            output_analysis_report (Optional[str]): The output file path where the XML analysis report will be written.
1014            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1015            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1016
1017        Returns:
1018            Tuple[Optional[bytes], Optional[bytes]]: A tuple of (protected_file_bytes, analysis_report_bytes).
1019        """
1020        # Validate arg types
1021        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1022            raise TypeError(f"input_file expected to be of type: str, bytes, bytearray, io.BytesIO. Got: {type(input_file).__name__}")
1023        if not isinstance(output_file, (type(None), str)):
1024            raise TypeError(f"output_file expected to be of type: None, str. Got: {type(output_file).__name__}")
1025        if not isinstance(output_analysis_report, (type(None), str)):
1026            raise TypeError(f"output_analysis_report expected to be of type: None, str. Got: {type(output_analysis_report).__name__}")
1027        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1028            raise TypeError(f"content_management_policy expected to be of type: None, str, bytes, bytearray, io.BytesIO, Policy. Got: {type(content_management_policy).__name__}")
1029        if not isinstance(raise_unsupported, bool):
1030            raise TypeError(f"raise_unsupported expected to be of type: bool. Got: {type(raise_unsupported).__name__}")
1031
1032        # Convert string path arguments to absolute paths
1033        if isinstance(input_file, str):
1034            if not os.path.isfile(input_file):
1035                raise FileNotFoundError(input_file)
1036            input_file = os.path.abspath(input_file)
1037        if isinstance(output_file, str):
1038            output_file = os.path.abspath(output_file)
1039            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1040        if isinstance(output_analysis_report, str):
1041            output_analysis_report = os.path.abspath(output_analysis_report)
1042            os.makedirs(os.path.dirname(output_analysis_report), exist_ok=True)
1043        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1044            content_management_policy = os.path.abspath(content_management_policy)
1045
1046        # Convert memory inputs to bytes
1047        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1048            input_file = utils.as_bytes(input_file)
1049
1050        with utils.CwdHandler(self.library_path):
1051            with self.new_session() as session:
1052                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1053                register_input = self.register_input(session, input_file)
1054                register_output = self.register_output(session, output_file)
1055                register_analysis = self.register_analysis(session, output_analysis_report)
1056
1057                status = self.run_session(session)
1058
1059                protected_file_bytes = None
1060                analysis_report_bytes = None
1061
1062                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1063                if status not in successes.success_codes:
1064                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1065                    if raise_unsupported:
1066                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1067
1068                # Get analysis report file bytes, even on processing failure
1069                if isinstance(output_analysis_report, str):
1070                    # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1071                    if not os.path.isfile(output_analysis_report):
1072                        log.error(f"Editor returned success code: {status} but no output file was found: {output_analysis_report}")
1073                    else:
1074                        with open(output_analysis_report, "rb") as f:
1075                            analysis_report_bytes = f.read()
1076                else:
1077                    # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1078                    if register_analysis.buffer and register_analysis.buffer_length:
1079                        analysis_report_bytes = utils.buffer_to_bytes(
1080                            register_analysis.buffer,
1081                            register_analysis.buffer_length
1082                        )
1083
1084                # On success, get protected file bytes
1085                if status in successes.success_codes:
1086                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1087                    # Get file bytes
1088                    if isinstance(output_file, str):
1089                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1090                        if not os.path.isfile(output_file):
1091                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1092                        else:
1093                            with open(output_file, "rb") as f:
1094                                protected_file_bytes = f.read()
1095                    else:
1096                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1097                        protected_file_bytes = utils.buffer_to_bytes(
1098                            register_output.buffer,
1099                            register_output.buffer_length
1100                        )
1101
1102                # Ensure memory allocated is not garbage collected
1103                content_management_policy, register_input, register_output, register_analysis
1104
1105                return protected_file_bytes, analysis_report_bytes
1106
1107    def create_config(self, default: Optional[str] = None, config: dict = {}) -> "glasswall.content_management.policies.policy.Policy":
1108        """ Creates a content management policy by querying the engine's default settings and
1109        applying the specified default and any explicit switch overrides on top.
1110
1111        Opens a temporary session solely to retrieve the engine's default policy via
1112        GW2GetPolicySettings, resolves the Editor policy against those defaults, then
1113        closes the session. No file processing is performed.
1114
1115        Args:
1116            default (Optional[str]): Default None. The value to apply to all content switches,
1117                overriding the engine defaults. (allow, disallow, sanitise). If None, the
1118                engine's own default values are returned unmodified.
1119            config (dict): A dictionary of explicit switch overrides. Keys are config element
1120                names (e.g. "pdfConfig"), values are dicts of switch name to switch value.
1121                Attribute overrides use the "@" prefix (e.g. "@libVersion").
1122
1123        Returns:
1124            policy (glasswall.content_management.policies.policy.Policy): A fully resolved
1125                Policy object. Calling str() or print() on it returns the XML representation.
1126
1127        Example:
1128            >>> print(editor.create_config())
1129            >>> print(editor.create_config(default="sanitise"))
1130            >>> print(editor.create_config(default="allow", config={"pdfConfig": {"metadata": "sanitise"}}))
1131        """
1132        editor_policy = glasswall.content_management.policies.Editor(default=default, config=config)
1133        with self.new_session() as session:
1134            return self._resolve_editor_policy(session, editor_policy)
1135
1136    def protect_and_analyse_directory(
1137        self,
1138        input_directory: str,
1139        output_directory: Optional[str] = None,
1140        analysis_directory: Optional[str] = None,
1141        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1142        raise_unsupported: bool = True,
1143    ):
1144        """ Recursively processes all files in a directory using protect and analyse mode with the given content management policy.
1145        Outputs are written to output_directory and analysis_directory maintaining the same structure as input_directory.
1146
1147        Args:
1148            input_directory (str): The input directory containing files to process.
1149            output_directory (Optional[str]): The output directory for protected files.
1150            analysis_directory (Optional[str]): The output directory for XML analysis reports.
1151            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply.
1152            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1153
1154        Returns:
1155            result_dict (dict): A dictionary mapping relative file paths to tuples of (protected_file_bytes, analysis_report_bytes).
1156        """
1157        result_dict = {}
1158        for input_file in utils.list_file_paths(input_directory):
1159            relative_path = os.path.relpath(input_file, input_directory)
1160            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
1161            output_analysis_report = None if analysis_directory is None else os.path.join(os.path.abspath(analysis_directory), relative_path + ".xml")
1162
1163            protected_file_bytes, analysis_report_bytes = self.protect_and_analyse_file(
1164                input_file=input_file,
1165                output_file=output_file,
1166                output_analysis_report=output_analysis_report,
1167                content_management_policy=content_management_policy,
1168                raise_unsupported=raise_unsupported,
1169            )
1170
1171            result_dict[relative_path] = (protected_file_bytes, analysis_report_bytes)
1172
1173        return result_dict
1174
1175    def _GW2RegisterExportFile(self, session: int, output_file: str):
1176        """ Register an export output file for the given session.
1177
1178        Args:
1179            session (int): The session integer.
1180            output_file (str): The export output file path.
1181
1182        Returns:
1183            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.
1184        """
1185        # API function declaration
1186        self.library.GW2RegisterExportFile.argtypes = [
1187            ct.c_size_t,  # Session_Handle session
1188            ct.c_char_p  # const char * exportFilePath
1189        ]
1190
1191        # Variable initialisation
1192        gw_return_object = glasswall.GwReturnObj()
1193        gw_return_object.session = ct.c_size_t(session)
1194        gw_return_object.output_file = ct.c_char_p(output_file.encode("utf-8"))
1195
1196        # API Call
1197        gw_return_object.status = self.library.GW2RegisterExportFile(
1198            gw_return_object.session,
1199            gw_return_object.output_file
1200        )
1201
1202        return gw_return_object
1203
1204    def _GW2RegisterExportMemory(self, session: int):
1205        """ Register an export output file in memory for the given session.
1206
1207        Args:
1208            session (int): The session integer.
1209
1210        Returns:
1211            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
1212        """
1213        # API function declaration
1214        self.library.GW2RegisterExportMemory.argtypes = [
1215            ct.c_size_t,  # Session_Handle session
1216            ct.POINTER(ct.c_void_p),  # char ** exportFileBuffer
1217            ct.POINTER(ct.c_size_t)  # size_t * exportLength
1218        ]
1219
1220        # Variable initialisation
1221        gw_return_object = glasswall.GwReturnObj()
1222        gw_return_object.session = ct.c_size_t(session)
1223        gw_return_object.buffer = ct.c_void_p()
1224        gw_return_object.buffer_length = ct.c_size_t()
1225
1226        # API call
1227        gw_return_object.status = self.library.GW2RegisterExportMemory(
1228            gw_return_object.session,
1229            ct.byref(gw_return_object.buffer),
1230            ct.byref(gw_return_object.buffer_length)
1231        )
1232
1233        return gw_return_object
1234
1235    def register_export(self, session: int, output_file: Optional[str] = None):
1236        """ Registers a file to be exported for the given session. The export file will be created during the session's run_session call.
1237
1238        Args:
1239            session (int): The session integer.
1240            output_file (Optional[str]): Default None. The file path where the export will be written. None exports the file in memory.
1241
1242        Returns:
1243            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attribute 'status' indicating the result of the function call and 'session', the session integer. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size.
1244        """
1245        if not isinstance(output_file, (type(None), str)):
1246            raise TypeError(output_file)
1247
1248        if isinstance(output_file, str):
1249            output_file = os.path.abspath(output_file)
1250
1251            result = self._GW2RegisterExportFile(session, output_file)
1252
1253        elif isinstance(output_file, type(None)):
1254            result = self._GW2RegisterExportMemory(session)
1255
1256        if result.status not in successes.success_codes:
1257            log.error(format_object(result))
1258            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
1259        else:
1260            log.debug(format_object(result))
1261
1262        return result
1263
1264    def export_file(
1265        self,
1266        input_file: Union[str, bytes, bytearray, io.BytesIO],
1267        output_file: Optional[str] = None,
1268        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1269        raise_unsupported: bool = True,
1270    ):
1271        """ Export a file, returning the .zip file bytes. The .zip file is written to output_file if it is provided.
1272
1273        Args:
1274            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
1275            output_file (Optional[str]): The output file path where the .zip file will be written.
1276            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1277            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1278
1279        Returns:
1280            file_bytes (bytes): The exported .zip file.
1281        """
1282        # Validate arg types
1283        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1284            raise TypeError(input_file)
1285        if not isinstance(output_file, (type(None), str)):
1286            raise TypeError(output_file)
1287        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1288            raise TypeError(content_management_policy)
1289        if not isinstance(raise_unsupported, bool):
1290            raise TypeError(raise_unsupported)
1291
1292        # Convert string path arguments to absolute paths
1293        if isinstance(input_file, str):
1294            if not os.path.isfile(input_file):
1295                raise FileNotFoundError(input_file)
1296            input_file = os.path.abspath(input_file)
1297        if isinstance(output_file, str):
1298            output_file = os.path.abspath(output_file)
1299            # make directories that do not exist
1300            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1301        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1302            content_management_policy = os.path.abspath(content_management_policy)
1303
1304        # Convert memory inputs to bytes
1305        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1306            input_file = utils.as_bytes(input_file)
1307
1308        with utils.CwdHandler(self.library_path):
1309            with self.new_session() as session:
1310                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1311                register_input = self.register_input(session, input_file)
1312                register_export = self.register_export(session, output_file)
1313                status = self.run_session(session)
1314
1315                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1316                if status not in successes.success_codes:
1317                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
1318                    if raise_unsupported:
1319                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1320                    else:
1321                        file_bytes = None
1322                else:
1323                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
1324                    # Get file bytes
1325                    if isinstance(output_file, str):
1326                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1327                        if not os.path.isfile(output_file):
1328                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1329                            file_bytes = None
1330                        else:
1331                            with open(output_file, "rb") as f:
1332                                file_bytes = f.read()
1333                    else:
1334                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1335                        file_bytes = utils.buffer_to_bytes(
1336                            register_export.buffer,
1337                            register_export.buffer_length
1338                        )
1339
1340                # Ensure memory allocated is not garbage collected
1341                content_management_policy, register_input, register_export
1342
1343                return file_bytes
1344
1345    def export_directory(
1346        self,
1347        input_directory: str,
1348        output_directory: Optional[str] = None,
1349        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1350        raise_unsupported: bool = True
1351    ):
1352        """ Exports all files in a directory and its subdirectories. The export files are written to output_directory maintaining the same directory structure as input_directory.
1353
1354        Args:
1355            input_directory (str): The input directory containing files to export.
1356            output_directory (Optional[str]): The output directory where the export files will be written, or None to not write files.
1357            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply.
1358            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1359
1360        Returns:
1361            export_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.
1362        """
1363        export_files_dict = {}
1364        # Call export_file on each file in input_directory to output_directory
1365        for input_file in utils.list_file_paths(input_directory):
1366            relative_path = os.path.relpath(input_file, input_directory) + ".zip"
1367            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
1368
1369            export_bytes = self.export_file(
1370                input_file=input_file,
1371                output_file=output_file,
1372                raise_unsupported=raise_unsupported,
1373                content_management_policy=content_management_policy,
1374            )
1375
1376            export_files_dict[relative_path] = export_bytes
1377
1378        return export_files_dict
1379
1380    def export_and_analyse_file(
1381        self,
1382        input_file: Union[str, bytes, bytearray, io.BytesIO],
1383        output_file: Optional[str] = None,
1384        output_analysis_report: Optional[str] = None,
1385        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1386        raise_unsupported: bool = True,
1387    ):
1388        """ Exports and analyses a file in a single session, returning both exported .zip bytes and analysis report bytes.
1389
1390        Args:
1391            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
1392            output_file (Optional[str]): The output file path where the .zip export will be written.
1393            output_analysis_report (Optional[str]): The output file path where the XML analysis report will be written.
1394            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1395            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1396
1397        Returns:
1398            Tuple[Optional[bytes], Optional[bytes]]: A tuple of (export_file_bytes, analysis_report_bytes).
1399        """
1400        # Validate arg types
1401        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1402            raise TypeError(f"input_file expected to be of type: str, bytes, bytearray, io.BytesIO. Got: {type(input_file).__name__}")
1403        if not isinstance(output_file, (type(None), str)):
1404            raise TypeError(f"output_file expected to be of type: None, str. Got: {type(output_file).__name__}")
1405        if not isinstance(output_analysis_report, (type(None), str)):
1406            raise TypeError(f"output_analysis_report expected to be of type: None, str. Got: {type(output_analysis_report).__name__}")
1407        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1408            raise TypeError(f"content_management_policy expected to be of type: None, str, bytes, bytearray, io.BytesIO, Policy. Got: {type(content_management_policy).__name__}")
1409        if not isinstance(raise_unsupported, bool):
1410            raise TypeError(f"raise_unsupported expected to be of type: bool. Got: {type(raise_unsupported).__name__}")
1411
1412        # Convert string path arguments to absolute paths
1413        if isinstance(input_file, str):
1414            if not os.path.isfile(input_file):
1415                raise FileNotFoundError(input_file)
1416            input_file = os.path.abspath(input_file)
1417        if isinstance(output_file, str):
1418            output_file = os.path.abspath(output_file)
1419            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1420        if isinstance(output_analysis_report, str):
1421            output_analysis_report = os.path.abspath(output_analysis_report)
1422            os.makedirs(os.path.dirname(output_analysis_report), exist_ok=True)
1423        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1424            content_management_policy = os.path.abspath(content_management_policy)
1425
1426        # Convert memory inputs to bytes
1427        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1428            input_file = utils.as_bytes(input_file)
1429
1430        with utils.CwdHandler(self.library_path):
1431            with self.new_session() as session:
1432                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1433                register_input = self.register_input(session, input_file)
1434                register_export = self.register_export(session, output_file)
1435                register_analysis = self.register_analysis(session, output_analysis_report)
1436
1437                status = self.run_session(session)
1438
1439                export_file_bytes = None
1440                analysis_report_bytes = None
1441
1442                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1443                if status not in successes.success_codes:
1444                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1445                    if raise_unsupported:
1446                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1447
1448                # Get analysis report file bytes, even on processing failure
1449                if isinstance(output_analysis_report, str):
1450                    # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1451                    if not os.path.isfile(output_analysis_report):
1452                        log.error(f"Editor returned success code: {status} but no output file was found: {output_analysis_report}")
1453                    else:
1454                        with open(output_analysis_report, "rb") as f:
1455                            analysis_report_bytes = f.read()
1456                else:
1457                    # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1458                    if register_analysis.buffer and register_analysis.buffer_length:
1459                        analysis_report_bytes = utils.buffer_to_bytes(
1460                            register_analysis.buffer,
1461                            register_analysis.buffer_length
1462                        )
1463
1464                # On success, get export file bytes
1465                if status in successes.success_codes:
1466                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1467                    # Get export file bytes
1468                    if isinstance(output_file, str):
1469                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1470                        if not os.path.isfile(output_file):
1471                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1472                        else:
1473                            with open(output_file, "rb") as f:
1474                                export_file_bytes = f.read()
1475                    else:
1476                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1477                        export_file_bytes = utils.buffer_to_bytes(
1478                            register_export.buffer,
1479                            register_export.buffer_length
1480                        )
1481
1482                # Ensure memory allocated is not garbage collected
1483                content_management_policy, register_input, register_export, register_analysis
1484
1485                return export_file_bytes, analysis_report_bytes
1486
1487    def export_and_analyse_directory(
1488        self,
1489        input_directory: str,
1490        output_directory: Optional[str] = None,
1491        analysis_directory: Optional[str] = None,
1492        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1493        raise_unsupported: bool = True,
1494    ):
1495        """ Recursively processes all files in a directory using export and analyse mode with the given content management policy.
1496        Outputs are written to output_directory and analysis_directory maintaining the same structure as input_directory.
1497
1498        Args:
1499            input_directory (str): The input directory containing files to process.
1500            output_directory (Optional[str]): The output directory for exported .zip files.
1501            analysis_directory (Optional[str]): The output directory for XML analysis reports.
1502            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply.
1503            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1504
1505        Returns:
1506            result_dict (dict): A dictionary mapping relative file paths to tuples of (export_file_bytes, analysis_report_bytes).
1507        """
1508        result_dict = {}
1509
1510        for input_file in utils.list_file_paths(input_directory):
1511            relative_path = os.path.relpath(input_file, input_directory)
1512            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path + ".zip")
1513            output_analysis_report = None if analysis_directory is None else os.path.join(os.path.abspath(analysis_directory), relative_path + ".xml")
1514
1515            export_file_bytes, analysis_report_bytes = self.export_and_analyse_file(
1516                input_file=input_file,
1517                output_file=output_file,
1518                output_analysis_report=output_analysis_report,
1519                content_management_policy=content_management_policy,
1520                raise_unsupported=raise_unsupported,
1521            )
1522
1523            result_dict[relative_path] = (export_file_bytes, analysis_report_bytes)
1524
1525        return result_dict
1526
1527    def _GW2RegisterImportFile(self, session: int, input_file: str):
1528        """ Register an import input file for the given session.
1529
1530        Args:
1531            session (int): The session integer.
1532            input_file (str): The input import file path.
1533
1534        Returns:
1535            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.
1536        """
1537        # API function declaration
1538        self.library.GW2RegisterImportFile.argtypes = [
1539            ct.c_size_t,  # Session_Handle session
1540            ct.c_char_p  # const char * importFilePath
1541        ]
1542
1543        # Variable initialisation
1544        gw_return_object = glasswall.GwReturnObj()
1545        gw_return_object.session = ct.c_size_t(session)
1546        gw_return_object.input_file = ct.c_char_p(input_file.encode("utf-8"))
1547
1548        # API Call
1549        gw_return_object.status = self.library.GW2RegisterImportFile(
1550            gw_return_object.session,
1551            gw_return_object.input_file
1552        )
1553
1554        return gw_return_object
1555
1556    def _GW2RegisterImportMemory(self, session: int, input_file: bytes):
1557        """ Register an import input file in memory for the given session.
1558
1559        Args:
1560            session (int): The session integer.
1561            input_file (str): The input import file in memory.
1562
1563        Returns:
1564            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
1565        """
1566        # API function declaration
1567        self.library.GW2RegisterImportMemory.argtypes = [
1568            ct.c_size_t,  # Session_Handle session
1569            ct.c_void_p,  # char * importFileBuffer
1570            ct.c_size_t  # size_t importLength
1571        ]
1572
1573        # Variable initialisation
1574        gw_return_object = glasswall.GwReturnObj()
1575        gw_return_object.session = ct.c_size_t(session)
1576        gw_return_object.buffer = ct.c_char_p(input_file)
1577        gw_return_object.buffer_length = ct.c_size_t(len(input_file))
1578
1579        # API call
1580        gw_return_object.status = self.library.GW2RegisterImportMemory(
1581            gw_return_object.session,
1582            gw_return_object.buffer,
1583            gw_return_object.buffer_length
1584        )
1585
1586        return gw_return_object
1587
1588    def register_import(self, session: int, input_file: Union[str, bytes, bytearray, io.BytesIO]):
1589        """ Registers a .zip file to be imported for the given session. The constructed file will be created during the session's run_session call.
1590
1591        Args:
1592            session (int): The session integer.
1593            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input import file path or bytes.
1594
1595        Returns:
1596            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attribute 'status' indicating the result of the function call. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size.
1597        """
1598        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO,)):
1599            raise TypeError(input_file)
1600
1601        if isinstance(input_file, str):
1602            if not os.path.isfile(input_file):
1603                raise FileNotFoundError(input_file)
1604
1605            input_file = os.path.abspath(input_file)
1606
1607            result = self._GW2RegisterImportFile(session, input_file)
1608
1609        elif isinstance(input_file, (bytes, bytearray, io.BytesIO,)):
1610            # Convert bytearray and io.BytesIO to bytes
1611            input_file = utils.as_bytes(input_file)
1612
1613            result = self._GW2RegisterImportMemory(session, input_file)
1614
1615        if result.status not in successes.success_codes:
1616            log.error(format_object(result))
1617            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
1618        else:
1619            log.debug(format_object(result))
1620
1621        return result
1622
1623    def import_file(
1624        self,
1625        input_file: Union[str, bytes, bytearray, io.BytesIO],
1626        output_file: Optional[str] = None,
1627        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1628        raise_unsupported: bool = True,
1629    ):
1630        """ Import a .zip file, constructs a file from the .zip file and returns the file bytes. The file is written to output_file if it is provided.
1631
1632        Args:
1633            input_file (Union[str, bytes, bytearray, io.BytesIO]): The .zip input file path or bytes.
1634            output_file (Optional[str]): The output file path where the constructed file will be written.
1635            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1636            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1637
1638        Returns:
1639            file_bytes (bytes): The imported file bytes.
1640        """
1641        # Validate arg types
1642        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1643            raise TypeError(input_file)
1644        if not isinstance(output_file, (type(None), str)):
1645            raise TypeError(output_file)
1646        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1647            raise TypeError(content_management_policy)
1648        if not isinstance(raise_unsupported, bool):
1649            raise TypeError(raise_unsupported)
1650
1651        # Convert string path arguments to absolute paths
1652        if isinstance(input_file, str):
1653            if not os.path.isfile(input_file):
1654                raise FileNotFoundError(input_file)
1655            input_file = os.path.abspath(input_file)
1656        if isinstance(output_file, str):
1657            output_file = os.path.abspath(output_file)
1658            # make directories that do not exist
1659            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1660        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1661            content_management_policy = os.path.abspath(content_management_policy)
1662
1663        # Convert memory inputs to bytes
1664        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1665            input_file = utils.as_bytes(input_file)
1666
1667        with utils.CwdHandler(self.library_path):
1668            with self.new_session() as session:
1669                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1670                register_import = self.register_import(session, input_file)
1671                register_output = self.register_output(session, output_file)
1672                status = self.run_session(session)
1673
1674                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1675                if status not in successes.success_codes:
1676                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
1677                    if raise_unsupported:
1678                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1679                    else:
1680                        file_bytes = None
1681                else:
1682                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
1683                    # Get file bytes
1684                    if isinstance(output_file, str):
1685                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1686                        if not os.path.isfile(output_file):
1687                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1688                            file_bytes = None
1689                        else:
1690                            with open(output_file, "rb") as f:
1691                                file_bytes = f.read()
1692                    else:
1693                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1694                        file_bytes = utils.buffer_to_bytes(
1695                            register_output.buffer,
1696                            register_output.buffer_length
1697                        )
1698
1699                # Ensure memory allocated is not garbage collected
1700                content_management_policy, register_import, register_output
1701
1702                return file_bytes
1703
1704    def import_directory(
1705        self,
1706        input_directory: str,
1707        output_directory: Optional[str] = None,
1708        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1709        raise_unsupported: bool = True,
1710    ):
1711        """ Imports all files in a directory and its subdirectories. Files are expected as .zip but this is not forced.
1712        The constructed files are written to output_directory maintaining the same directory structure as input_directory.
1713
1714        Args:
1715            input_directory (str): The input directory containing files to import.
1716            output_directory (Optional[str]): The output directory where the constructed files will be written, or None to not write files.
1717            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply.
1718            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1719
1720        Returns:
1721            import_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.
1722        """
1723        import_files_dict = {}
1724        # Call import_file on each file in input_directory to output_directory
1725        for input_file in utils.list_file_paths(input_directory):
1726            relative_path = os.path.relpath(input_file, input_directory)
1727            # Remove .zip extension from relative_path
1728            if relative_path.endswith(".zip"):
1729                relative_path = os.path.splitext(relative_path)[0]
1730            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
1731
1732            import_bytes = self.import_file(
1733                input_file=input_file,
1734                output_file=output_file,
1735                raise_unsupported=raise_unsupported,
1736                content_management_policy=content_management_policy,
1737            )
1738
1739            import_files_dict[relative_path] = import_bytes
1740
1741        return import_files_dict
1742
1743    def import_and_analyse_file(
1744        self,
1745        input_file: Union[str, bytes, bytearray, io.BytesIO],
1746        output_file: Optional[str] = None,
1747        output_analysis_report: Optional[str] = None,
1748        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1749        raise_unsupported: bool = True,
1750    ):
1751        """ Imports and analyses a file in a single session, returning both imported file bytes and analysis report bytes.
1752
1753        Args:
1754            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
1755            output_file (Optional[str]): The output file path where the imported file will be written.
1756            output_analysis_report (Optional[str]): The output file path where the XML analysis report will be written.
1757            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1758            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1759
1760        Returns:
1761            Tuple[Optional[bytes], Optional[bytes]]: A tuple of (import_file_bytes, analysis_report_bytes).
1762        """
1763        # Validate arg types
1764        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1765            raise TypeError(f"input_file expected to be of type: str, bytes, bytearray, io.BytesIO. Got: {type(input_file).__name__}")
1766        if not isinstance(output_file, (type(None), str)):
1767            raise TypeError(f"output_file expected to be of type: None, str. Got: {type(output_file).__name__}")
1768        if not isinstance(output_analysis_report, (type(None), str)):
1769            raise TypeError(f"output_analysis_report expected to be of type: None, str. Got: {type(output_analysis_report).__name__}")
1770        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1771            raise TypeError(f"content_management_policy expected to be of type: None, str, bytes, bytearray, io.BytesIO, Policy. Got: {type(content_management_policy).__name__}")
1772        if not isinstance(raise_unsupported, bool):
1773            raise TypeError(f"raise_unsupported expected to be of type: bool. Got: {type(raise_unsupported).__name__}")
1774
1775        # Convert string path arguments to absolute paths
1776        if isinstance(input_file, str):
1777            if not os.path.isfile(input_file):
1778                raise FileNotFoundError(input_file)
1779            input_file = os.path.abspath(input_file)
1780        if isinstance(output_file, str):
1781            output_file = os.path.abspath(output_file)
1782            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1783        if isinstance(output_analysis_report, str):
1784            output_analysis_report = os.path.abspath(output_analysis_report)
1785            os.makedirs(os.path.dirname(output_analysis_report), exist_ok=True)
1786        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1787            content_management_policy = os.path.abspath(content_management_policy)
1788
1789        # Convert memory inputs to bytes
1790        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1791            input_file = utils.as_bytes(input_file)
1792
1793        with utils.CwdHandler(self.library_path):
1794            with self.new_session() as session:
1795                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1796                register_import = self.register_import(session, input_file)
1797                register_output = self.register_output(session, output_file)
1798                register_analysis = self.register_analysis(session, output_analysis_report)
1799
1800                status = self.run_session(session)
1801
1802                import_file_bytes = None
1803                analysis_report_bytes = None
1804
1805                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1806                if status not in successes.success_codes:
1807                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1808                    if raise_unsupported:
1809                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1810
1811                # Get analysis report file bytes, even on processing failure
1812                if isinstance(output_analysis_report, str):
1813                    # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1814                    if not os.path.isfile(output_analysis_report):
1815                        log.error(f"Editor returned success code: {status} but no output file was found: {output_analysis_report}")
1816                    else:
1817                        with open(output_analysis_report, "rb") as f:
1818                            analysis_report_bytes = f.read()
1819                else:
1820                    # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1821                    if register_analysis.buffer and register_analysis.buffer_length:
1822                        analysis_report_bytes = utils.buffer_to_bytes(
1823                            register_analysis.buffer,
1824                            register_analysis.buffer_length
1825                        )
1826
1827                # On success, get import file bytes
1828                if status in successes.success_codes:
1829                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1830                    # Get import file bytes
1831                    if isinstance(output_file, str):
1832                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1833                        if not os.path.isfile(output_file):
1834                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1835                        else:
1836                            with open(output_file, "rb") as f:
1837                                import_file_bytes = f.read()
1838                    else:
1839                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1840                        import_file_bytes = utils.buffer_to_bytes(
1841                            register_output.buffer,
1842                            register_output.buffer_length
1843                        )
1844
1845                # Ensure memory allocated is not garbage collected
1846                content_management_policy, register_import, register_output, register_analysis
1847
1848                return import_file_bytes, analysis_report_bytes
1849
1850    def import_and_analyse_directory(
1851        self,
1852        input_directory: str,
1853        output_directory: Optional[str] = None,
1854        analysis_directory: Optional[str] = None,
1855        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1856        raise_unsupported: bool = True,
1857    ):
1858        """ Recursively processes all files in a directory using import and analyse mode with the given content management policy.
1859        Outputs are written to output_directory and analysis_directory maintaining the same structure as input_directory.
1860
1861        Args:
1862            input_directory (str): The input directory containing export .zip files to process.
1863            output_directory (Optional[str]): The output directory for imported files.
1864            analysis_directory (Optional[str]): The output directory for XML analysis reports.
1865            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply.
1866            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1867
1868        Returns:
1869            result_dict (dict): A dictionary mapping relative file paths to tuples of (import_file_bytes, analysis_report_bytes).
1870        """
1871        result_dict = {}
1872
1873        for input_file in utils.list_file_paths(input_directory):
1874            relative_path = os.path.relpath(input_file, input_directory)
1875            # Remove .zip extension from relative_path
1876            if relative_path.endswith(".zip"):
1877                relative_path = os.path.splitext(relative_path)[0]
1878            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
1879            output_analysis_report = None if analysis_directory is None else os.path.join(os.path.abspath(analysis_directory), relative_path + ".xml")
1880
1881            import_file_bytes, analysis_report_bytes = self.import_and_analyse_file(
1882                input_file=input_file,
1883                output_file=output_file,
1884                output_analysis_report=output_analysis_report,
1885                content_management_policy=content_management_policy,
1886                raise_unsupported=raise_unsupported,
1887            )
1888
1889            result_dict[relative_path] = (import_file_bytes, analysis_report_bytes)
1890
1891        return result_dict
1892
1893    def _GW2FileErrorMsg(self, session: int):
1894        """ Retrieve the Glasswall Session Process error message.
1895
1896        Args:
1897            session (int): The session integer.
1898
1899        Returns:
1900            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status', 'error_message'.
1901        """
1902        # API function declaration
1903        self.library.GW2FileErrorMsg.argtypes = [
1904            ct.c_size_t,  # Session_Handle session
1905            ct.POINTER(ct.c_void_p),  # char **errorMsgBuffer
1906            ct.POINTER(ct.c_size_t)  # size_t *errorMsgBufferLength
1907        ]
1908
1909        # Variable initialisation
1910        gw_return_object = glasswall.GwReturnObj()
1911        gw_return_object.session = ct.c_size_t(session)
1912        gw_return_object.buffer = ct.c_void_p()
1913        gw_return_object.buffer_length = ct.c_size_t()
1914
1915        # API call
1916        gw_return_object.status = self.library.GW2FileErrorMsg(
1917            gw_return_object.session,
1918            ct.byref(gw_return_object.buffer),
1919            ct.byref(gw_return_object.buffer_length)
1920        )
1921
1922        # Editor wrote to a buffer, convert it to bytes
1923        error_bytes = utils.buffer_to_bytes(
1924            gw_return_object.buffer,
1925            gw_return_object.buffer_length
1926        )
1927
1928        gw_return_object.error_message = error_bytes.decode()
1929
1930        return gw_return_object
1931
1932    @functools.lru_cache()
1933    def file_error_message(self, session: int) -> str:
1934        """ Retrieve the Glasswall Session Process error message.
1935
1936        Args:
1937            session (int): The session integer.
1938
1939        Returns:
1940            error_message (str): The Glasswall Session Process error message.
1941        """
1942        # Validate arg types
1943        if not isinstance(session, int):
1944            raise TypeError(session)
1945
1946        result = self._GW2FileErrorMsg(session)
1947
1948        if result.status not in successes.success_codes:
1949            log.error(format_object(result))
1950            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
1951        else:
1952            log.debug(format_object(result))
1953
1954        return result.error_message
1955
1956    def GW2GetFileType(self, session: int, file_type_id):
1957        """ Retrieve the file type as a string.
1958
1959        Args:
1960            session (int): The session integer.
1961            file_type_id (int): The file type id.
1962
1963        Returns:
1964            file_type (str): The formal file name for the corresponding file id.
1965        """
1966        # Validate arg types
1967        if not isinstance(session, int):
1968            raise TypeError(session)
1969
1970        # API function declaration
1971        self.library.GW2GetFileType.argtypes = [
1972            ct.c_size_t,
1973            ct.c_size_t,
1974            ct.POINTER(ct.c_size_t),
1975            ct.POINTER(ct.c_void_p)
1976        ]
1977
1978        # Variable initialisation
1979        ct_session = ct.c_size_t(session)
1980        ct_file_type = ct.c_size_t(file_type_id)
1981        ct_buffer_length = ct.c_size_t()
1982        ct_buffer = ct.c_void_p()
1983
1984        # API call
1985        status = self.library.GW2GetFileType(
1986            ct_session,
1987            ct_file_type,
1988            ct.byref(ct_buffer_length),
1989            ct.byref(ct_buffer)
1990        )
1991
1992        if status not in successes.success_codes:
1993            log.error(f"\n\tsession: {session}\n\tstatus: {status}")
1994            raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1995        else:
1996            log.debug(f"\n\tsession: {session}\n\tstatus: {status}")
1997
1998        # Editor wrote to a buffer, convert it to bytes
1999        file_type_bytes = utils.buffer_to_bytes(
2000            ct_buffer,
2001            ct_buffer_length
2002        )
2003
2004        file_type = file_type_bytes.decode()
2005
2006        return file_type
2007
2008    def GW2GetFileTypeID(self, session: int, file_type_str):
2009        """ Retrieve the Glasswall file type id given a file type string.
2010
2011        Args:
2012            session (int): The session integer.
2013            file_type_str (str): The file type as a string.
2014
2015        Returns:
2016            file_type_id (str): The Glasswall file type id for the specified file type.
2017        """
2018        # Validate arg types
2019        if not isinstance(session, int):
2020            raise TypeError(session)
2021
2022        # API function declaration
2023        self.library.GW2GetFileTypeID.argtypes = [
2024            ct.c_size_t,
2025            ct.c_char_p,
2026            ct.POINTER(ct.c_size_t),
2027            ct.POINTER(ct.c_void_p)
2028        ]
2029
2030        # Variable initialisation
2031        ct_session = ct.c_size_t(session)
2032        ct_file_type = ct.c_char_p(file_type_str.encode('utf-8'))
2033        ct_buffer_length = ct.c_size_t()
2034        ct_buffer = ct.c_void_p()
2035
2036        # API call
2037        status = self.library.GW2GetFileTypeID(
2038            ct_session,
2039            ct_file_type,
2040            ct.byref(ct_buffer_length),
2041            ct.byref(ct_buffer)
2042        )
2043
2044        if status not in successes.success_codes:
2045            log.error(f"\n\tsession: {session}\n\tstatus: {status}")
2046            raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
2047        else:
2048            log.debug(f"\n\tsession: {session}\n\tstatus: {status}")
2049
2050        # Editor wrote to a buffer, convert it to bytes
2051        file_type_bytes = utils.buffer_to_bytes(
2052            ct_buffer,
2053            ct_buffer_length
2054        )
2055
2056        file_type_id = file_type_bytes.decode()
2057
2058        return file_type_id
2059
2060    def get_file_type_info(self, file_type: Union[str, int]):
2061        """ Retrieve information about a file type based on its identifier.
2062
2063        Args:
2064            file_type (Union[str, int]): The file type identifier. This can be either a string representing a file
2065            extension (e.g. 'bmp') or an integer corresponding to a file type (e.g. 29).
2066
2067        Returns:
2068            - file_type_info (Union[int, str]): Depending on the input 'file_type':
2069                - If `file_type` is a string (e.g. 'bmp'):
2070                    - If the file type is recognised, returns an integer corresponding to that file type.
2071                    - If the file type is not recognised, returns 0.
2072                - If `file_type` is an integer (e.g. 29):
2073                    - If the integer corresponds to a recognised file type, returns a more detailed string description
2074                        of the file type (e.g. 'BMP Image').
2075                    - If the integer does not match any recognised file type, returns an empty string.
2076        """
2077        # Validate arg types
2078        if not isinstance(file_type, (str, int)):
2079            raise TypeError(file_type)
2080
2081        with utils.CwdHandler(self.library_path):
2082            with self.new_session() as session:
2083
2084                if isinstance(file_type, int):
2085                    file_type_info = self.GW2GetFileType(session, file_type)
2086                if isinstance(file_type, str):
2087                    file_type_info = self.GW2GetFileTypeID(session, file_type)
2088
2089                return file_type_info
2090
2091    @utils.deprecated_function(replacement_function=get_file_type_info)
2092    def get_file_info(self, *args, **kwargs):
2093        """ Deprecated in 1.0.6. Use get_file_type_info. """
2094        pass
2095
2096    def _GW2RegisterReportFile(self, session: int, output_file: str):
2097        """ Register an output report file path for the given session.
2098
2099        Args:
2100            session (int): The session integer.
2101            output_file (str): The file path of the output report file.
2102
2103        Returns:
2104            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.
2105        """
2106        # API function declaration
2107        self.library.GW2RegisterReportFile.argtypes = [
2108            ct.c_size_t,  # Session_Handle session
2109            ct.c_char_p,  # const char * reportFilePathName
2110        ]
2111
2112        # Variable initialisation
2113        gw_return_object = glasswall.GwReturnObj()
2114        gw_return_object.session = ct.c_size_t(session)
2115        gw_return_object.output_file = ct.c_char_p(output_file.encode("utf-8"))
2116
2117        # API call
2118        gw_return_object.status = self.library.GW2RegisterReportFile(
2119            gw_return_object.session,
2120            gw_return_object.output_file
2121        )
2122
2123        return gw_return_object
2124
2125    def register_report_file(self, session: int, output_file: str):
2126        """ Register the report file path for the given session.
2127
2128        Args:
2129            session (int): The session integer.
2130            output_file (str): The file path of the report file.
2131
2132        Returns:
2133            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.
2134        """
2135        # Validate arg types
2136        if not isinstance(session, int):
2137            raise TypeError(session)
2138        if not isinstance(output_file, (type(None), str)):
2139            raise TypeError(output_file)
2140
2141        result = self._GW2RegisterReportFile(session, output_file)
2142
2143        if result.status not in successes.success_codes:
2144            log.error(format_object(result))
2145            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2146        else:
2147            log.debug(format_object(result))
2148
2149        return result
2150
2151    def _GW2GetIdInfo(self, session: int, issue_id: int):
2152        """ Retrieves the group description for the given Issue ID. e.g. issue_id 96 returns "Document Processing Instances"
2153
2154        Args:
2155            session (int): The session integer.
2156            issue_id (int): The issue id.
2157            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
2158
2159        Returns:
2160            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'issue_id', 'buffer_length', 'buffer', 'status', 'id_info'.
2161        """
2162        # API function declaration
2163        self.library.GW2GetIdInfo.argtypes = [
2164            ct.c_size_t,  # Session_Handle session
2165            ct.c_size_t,  # size_t issueId
2166            ct.POINTER(ct.c_size_t),  # size_t * bufferLength
2167            ct.POINTER(ct.c_void_p)  # char ** outputBuffer
2168        ]
2169
2170        # Variable initialisation
2171        gw_return_object = glasswall.GwReturnObj()
2172        gw_return_object.session = ct.c_size_t(session)
2173        gw_return_object.issue_id = ct.c_size_t(issue_id)
2174        gw_return_object.buffer_length = ct.c_size_t()
2175        gw_return_object.buffer = ct.c_void_p()
2176
2177        # API call
2178        gw_return_object.status = self.library.GW2GetIdInfo(
2179            gw_return_object.session,
2180            gw_return_object.issue_id,
2181            ct.byref(gw_return_object.buffer_length),
2182            ct.byref(gw_return_object.buffer)
2183        )
2184
2185        # Editor wrote to a buffer, convert it to bytes
2186        id_info_bytes = utils.buffer_to_bytes(
2187            gw_return_object.buffer,
2188            gw_return_object.buffer_length
2189        )
2190
2191        gw_return_object.id_info = id_info_bytes.decode()
2192
2193        return gw_return_object
2194
2195    def get_id_info(self, issue_id: int, raise_unsupported: bool = True):
2196        """ Retrieves the group description for the given Issue ID. e.g. issue_id 96 returns "Document Processing Instances"
2197
2198        Args:
2199            issue_id (int): The issue id.
2200            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
2201
2202        Returns:
2203            id_info (str): The group description for the given Issue ID.
2204        """
2205        # Validate arg types
2206        if not isinstance(issue_id, int):
2207            raise TypeError(issue_id)
2208
2209        with utils.CwdHandler(self.library_path):
2210            with self.new_session() as session:
2211                result = self._GW2GetIdInfo(session, issue_id)
2212
2213                if result.status not in successes.success_codes:
2214                    log.error(format_object(result))
2215                    if raise_unsupported:
2216                        raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2217                else:
2218                    log.debug(format_object(result))
2219
2220                return result.id_info
2221
2222    def _GW2GetAllIdInfo(self, session: int):
2223        """ Retrieves the XML containing all the Issue ID ranges with their group descriptions
2224
2225        Args:
2226            session (int): The session integer.
2227
2228        Returns:
2229            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'analysis_format', 'status', 'all_id_info'.
2230        """
2231
2232        # API function declaration
2233        self.library.GW2GetAllIdInfo.argtypes = [
2234            ct.c_size_t,  # Session_Handle session
2235            ct.POINTER(ct.c_size_t),  # size_t * bufferLength
2236            ct.POINTER(ct.c_void_p)  # char ** outputBuffer
2237        ]
2238
2239        # Variable initialisation
2240        # The extracted issue Id information is stored in the analysis report, register an analysis session.
2241        gw_return_object = self._GW2RegisterAnalysisMemory(session)
2242
2243        # API call
2244        gw_return_object.status = self.library.GW2GetAllIdInfo(
2245            gw_return_object.session,
2246            ct.byref(gw_return_object.buffer_length),
2247            ct.byref(gw_return_object.buffer)
2248        )
2249
2250        # Editor wrote to a buffer, convert it to bytes
2251        all_id_info_bytes = utils.buffer_to_bytes(
2252            gw_return_object.buffer,
2253            gw_return_object.buffer_length
2254        )
2255
2256        gw_return_object.all_id_info = all_id_info_bytes.decode()
2257
2258        return gw_return_object
2259
2260    def get_all_id_info(self, output_file: Optional[str] = None, raise_unsupported: bool = True) -> str:
2261        """ Retrieves the XML containing all the Issue ID ranges with their group descriptions
2262
2263        Args:
2264            output_file (Optional[str]): The output file path where the analysis file will be written.
2265            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
2266
2267        Returns:
2268            all_id_info (str): A string XML analysis report containing all id info.
2269        """
2270        # Validate arg types
2271        if not isinstance(output_file, (type(None), str)):
2272            raise TypeError(output_file)
2273        if isinstance(output_file, str):
2274            output_file = os.path.abspath(output_file)
2275
2276        with utils.CwdHandler(self.library_path):
2277            with self.new_session() as session:
2278                result = self._GW2GetAllIdInfo(session)
2279
2280                if result.status not in successes.success_codes:
2281                    log.error(format_object(result))
2282                    if raise_unsupported:
2283                        raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2284                else:
2285                    log.debug(format_object(result))
2286
2287                if isinstance(output_file, str):
2288                    # GW2GetAllIdInfo is memory only, write to file
2289                    # make directories that do not exist
2290                    os.makedirs(os.path.dirname(output_file), exist_ok=True)
2291                    with open(output_file, "w") as f:
2292                        f.write(result.all_id_info)
2293
2294                return result.all_id_info
2295
2296    def _GW2FileSessionStatus(self, session: int):
2297        """ Retrieves the Glasswall Session Status. Also gives a high level indication of the processing that was carried out on the last document processed by the library
2298
2299        Args:
2300            session (int): The session integer.
2301            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
2302
2303        Returns:
2304            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'session_status', 'buffer', 'buffer_length', 'status', 'message'.
2305        """
2306        # API function declaration
2307        self.library.GW2FileSessionStatus.argtypes = [
2308            ct.c_size_t,  # Session_Handle session
2309            ct.POINTER(ct.c_int),  # int *glasswallSessionStatus
2310            ct.POINTER(ct.c_void_p),  # char **statusMsgBuffer
2311            ct.POINTER(ct.c_size_t)  # size_t *statusbufferLength
2312        ]
2313
2314        # Variable initialisation
2315        gw_return_object = glasswall.GwReturnObj()
2316        gw_return_object.session = ct.c_size_t(session)
2317        gw_return_object.session_status = ct.c_int()
2318        gw_return_object.buffer = ct.c_void_p()
2319        gw_return_object.buffer_length = ct.c_size_t()
2320
2321        # API call
2322        gw_return_object.status = self.library.GW2FileSessionStatus(
2323            gw_return_object.session,
2324            ct.byref(gw_return_object.session_status),
2325            ct.byref(gw_return_object.buffer),
2326            ct.byref(gw_return_object.buffer_length)
2327        )
2328
2329        # Convert session_status to int
2330        gw_return_object.session_status = gw_return_object.session_status.value
2331
2332        # Editor wrote to a buffer, convert it to bytes
2333        message_bytes = utils.buffer_to_bytes(
2334            gw_return_object.buffer,
2335            gw_return_object.buffer_length
2336        )
2337        gw_return_object.message = message_bytes.decode()
2338
2339        return gw_return_object
2340
2341    def file_session_status_message(self, session: int, raise_unsupported: bool = True) -> str:
2342        """ Retrieves the Glasswall session status message. Gives a high level indication of the processing that was carried out.
2343
2344        Args:
2345            session (int): The session integer.
2346            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
2347
2348        Returns:
2349            result.message (str):The file session status message.
2350        """
2351        # Validate arg types
2352        if not isinstance(session, int):
2353            raise TypeError(session)
2354
2355        result = self._GW2FileSessionStatus(session)
2356
2357        if result.status not in successes.success_codes:
2358            log.error(format_object(result))
2359            if raise_unsupported:
2360                raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2361        else:
2362            log.debug(format_object(result))
2363
2364        return result.message
2365
2366    def _GW2LicenceDetails(self, session: int):
2367        """ Returns a human readable string containing licence details.
2368
2369        Args:
2370            session (int): The session integer.
2371
2372        Returns:
2373            licence_details (str): A human readable string representing the relevant information contained in the licence.
2374        """
2375        # API function declaration
2376        self.library.GW2LicenceDetails.argtypes = [ct.c_size_t]
2377        self.library.GW2LicenceDetails.restype = ct.c_char_p
2378
2379        # Variable initialisation
2380        ct_session = ct.c_size_t(session)
2381
2382        # API call
2383        licence_details = self.library.GW2LicenceDetails(ct_session)
2384
2385        # Convert to Python string
2386        licence_details = ct.string_at(licence_details).decode()
2387
2388        return licence_details
2389
2390    def licence_details(self):
2391        """ Returns a string containing details of the licence.
2392
2393        Returns:
2394            result (str): A string containing details of the licence.
2395        """
2396        with self.new_session() as session:
2397            result = self._GW2LicenceDetails(session)
2398
2399            log.debug(f"\n\tsession: {session}\n\tGW2LicenceDetails: {result}")
2400
2401        return result
2402
2403    def _GW2RegisterExportTextDumpMemory(self, session: int):
2404        """ Registers an export text dump to be written in memory.
2405
2406        Args:
2407            session (int): The session integer.
2408
2409        Returns:
2410            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
2411        """
2412        # API function declaration
2413        self.library.GW2RegisterExportTextDumpMemory.argtypes = [
2414            ct.c_size_t,  # Session_Handle session
2415            ct.POINTER(ct.c_void_p),  # char ** exportTextDumpFileBuffer
2416            ct.POINTER(ct.c_size_t)  # size_t * exportTextDumpLength
2417        ]
2418
2419        # Variable initialisation
2420        gw_return_object = glasswall.GwReturnObj()
2421        gw_return_object.session = ct.c_size_t(session)
2422        gw_return_object.buffer = ct.c_void_p()
2423        gw_return_object.buffer_length = ct.c_size_t()
2424
2425        # API call
2426        gw_return_object.status = self.library.GW2RegisterExportTextDumpMemory(
2427            gw_return_object.session,
2428            ct.byref(gw_return_object.buffer),
2429            ct.byref(gw_return_object.buffer_length)
2430        )
2431
2432        return gw_return_object
2433
2434    def _GW2RegisterExportTextDumpFile(self, session: int, output_file: str):
2435        """ Registers an export text dump to be written to file.
2436
2437        Args:
2438            session (int): The session integer.
2439            output_file (str): The file path of the text dump file.
2440
2441        Returns:
2442            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.
2443        """
2444        # API function declaration
2445        self.library.GW2RegisterExportTextDumpFile.argtypes = [
2446            ct.c_size_t,  # Session_Handle session
2447            ct.c_char_p  # const char * textDumpFilePathName
2448        ]
2449
2450        # Variable initialisation
2451        gw_return_object = glasswall.GwReturnObj()
2452        gw_return_object.session = ct.c_size_t(session)
2453        gw_return_object.output_file = ct.c_char_p(output_file.encode("utf-8"))
2454
2455        # API call
2456        gw_return_object.status = self.library.GW2RegisterExportTextDumpFile(
2457            gw_return_object.session,
2458            gw_return_object.output_file
2459        )
2460
2461        return gw_return_object
2462
2463    def _GW2RegisterLicenceFile(self, session: int, input_file: str):
2464        """ Registers a "gwkey.lic" licence from file path.
2465
2466        Args:
2467            session (int): The session integer.
2468            input_file (str): The "gwkey.lic" licence input file path.
2469
2470        Returns:
2471            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'status'.
2472        """
2473        # API function declaration
2474        self.library.GW2RegisterLicenceFile.argtypes = [
2475            ct.c_size_t,  # Session_Handle session
2476            ct.c_char_p,  # const char *filename
2477        ]
2478
2479        # Variable initialisation
2480        gw_return_object = glasswall.GwReturnObj()
2481        gw_return_object.session = ct.c_size_t(session)
2482        gw_return_object.input_file = ct.c_char_p(input_file.encode("utf-8"))
2483
2484        # API call
2485        gw_return_object.status = self.library.GW2RegisterLicenceFile(
2486            gw_return_object.session,
2487            gw_return_object.input_file,
2488        )
2489
2490        return gw_return_object
2491
2492    def _GW2RegisterLicenceMemory(self, session: int, input_file: bytes):
2493        """ Registers a "gwkey.lic" licence from memory.
2494
2495        Args:
2496            session (int): The session integer.
2497            input_file (bytes): The "gwkey.lic" licence input file.
2498
2499        Returns:
2500            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
2501        """
2502        # API function declaration
2503        self.library.GW2RegisterLicenceMemory.argtypes = [
2504            ct.c_size_t,  # Session_Handle session
2505            ct.c_char_p,  # const char *filename
2506            ct.c_size_t,  # size_t licenceLength
2507        ]
2508
2509        # Variable initialisation
2510        gw_return_object = glasswall.GwReturnObj()
2511        gw_return_object.session = ct.c_size_t(session)
2512        gw_return_object.buffer = ct.c_char_p(input_file)
2513        gw_return_object.buffer_length = ct.c_size_t(len(input_file))
2514
2515        # API call
2516        gw_return_object.status = self.library.GW2RegisterLicenceMemory(
2517            gw_return_object.session,
2518            gw_return_object.buffer,
2519            gw_return_object.buffer_length
2520        )
2521
2522        return gw_return_object
2523
2524    def register_licence(self, session: int, input_file: Union[str, bytes, bytearray, io.BytesIO]):
2525        """ Registers a "gwkey.lic" licence from file path or memory.
2526
2527        Args:
2528            session (int): The session integer.
2529            input_file (Union[str, bytes, bytearray, io.BytesIO]): The "gwkey.lic" licence. It can be provided as a file path (str), bytes, bytearray, or a BytesIO object.
2530
2531        Returns:
2532            - result (glasswall.GwReturnObj): Depending on the input 'input_file':
2533                - If input_file is a str file path:
2534                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'status'.
2535
2536                - If input_file is a file in memory:
2537                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
2538        """
2539        if isinstance(input_file, str):
2540            if not os.path.isfile(input_file):
2541                raise FileNotFoundError(input_file)
2542
2543            input_file = os.path.abspath(input_file)
2544
2545            result = self._GW2RegisterLicenceFile(session, input_file)
2546
2547        elif isinstance(input_file, (bytes, bytearray, io.BytesIO,)):
2548            # Convert bytearray and io.BytesIO to bytes
2549            input_file = utils.as_bytes(input_file)
2550
2551            result = self._GW2RegisterLicenceMemory(session, input_file)
2552
2553        else:
2554            raise TypeError(input_file)
2555
2556        if result.status not in successes.success_codes:
2557            log.error(format_object(result))
2558            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2559        else:
2560            log.debug(format_object(result))
2561
2562        return result

A high level Python wrapper for Glasswall Editor / Core2.

Editor( library_path: str, licence: str | bytes | bytearray | _io.BytesIO = None)
22    def __init__(self, library_path: str, licence: Union[str, bytes, bytearray, io.BytesIO] = None):
23        """ Initialise the Editor instance.
24
25        Args:
26            library_path (str): The file or directory path to the Editor library.
27            licence (str, bytes, bytearray, or io.BytesIO, optional): The licence file content or path. This can be:
28                - A string representing the file path to the licence.
29                - A `bytes` or `bytearray` object containing the licence data.
30                - An `io.BytesIO` object for in-memory licence data.
31                If not specified, it is assumed that the licence file is located in the same directory as the `library_path`.
32        """
33        super().__init__(library_path)
34        self.library = self.load_library(os.path.abspath(library_path))
35        self.licence = licence
36
37        # Validate killswitch has not activated
38        self.validate_licence()
39
40        log.info(f"Loaded Glasswall {self.__class__.__name__} version {self.version()} from {self.library_path}")

Initialise the Editor instance.

Args: library_path (str): The file or directory path to the Editor library. licence (str, bytes, bytearray, or io.BytesIO, optional): The licence file content or path. This can be: - A string representing the file path to the licence. - A bytes or bytearray object containing the licence data. - An io.BytesIO object for in-memory licence data. If not specified, it is assumed that the licence file is located in the same directory as the library_path.

library
licence
def validate_licence(self):
42    def validate_licence(self):
43        """ Validates the licence of the library by checking the licence details.
44
45        Raises:
46            LicenceExpired: If the licence has expired or could not be validated.
47        """
48        licence_details = self.licence_details()
49
50        bad_details = [
51            "Unable to Read Licence Key File",
52            "Licence File Missing Required Contents",
53            "Licence Expired",
54        ]
55
56        if any(bad_detail.lower() in licence_details.lower() for bad_detail in bad_details):
57            # bad_details found in licence_details
58            log.error(f"{self.__class__.__name__} licence validation failed. Licence details:\n{licence_details}")
59            raise errors.LicenceExpired(licence_details)
60        else:
61            log.debug(f"{self.__class__.__name__} licence validated successfully. Licence details:\n{licence_details}")

Validates the licence of the library by checking the licence details.

Raises: LicenceExpired: If the licence has expired or could not be validated.

def version(self):
63    def version(self):
64        """ Returns the Glasswall library version.
65
66        Returns:
67            version (str): The Glasswall library version.
68        """
69        # API function declaration
70        self.library.GW2LibVersion.restype = ct.c_char_p
71
72        # API call
73        version = self.library.GW2LibVersion()
74
75        # Convert to Python string
76        version = ct.string_at(version).decode()
77
78        return version

Returns the Glasswall library version.

Returns: version (str): The Glasswall library version.

def open_session(self):
80    def open_session(self):
81        """ Open a new Glasswall session.
82
83        Returns:
84            session (int): An incrementing integer repsenting the current session.
85        """
86        # API call
87        session = self.library.GW2OpenSession()
88
89        log.debug(f"\n\tsession: {session}")
90
91        if self.licence:
92            # Must register licence on each GW2OpenSession if the licence is not in the same directory as the library
93            self.register_licence(session, self.licence)
94
95        return session

Open a new Glasswall session.

Returns: session (int): An incrementing integer repsenting the current session.

def close_session(self, session: int) -> int:
 97    def close_session(self, session: int) -> int:
 98        """ Close the Glasswall session. All resources allocated by the session will be destroyed.
 99
100        Args:
101            session (int): The session to close.
102
103        Returns:
104            status (int): The status code of the function call.
105        """
106        if not isinstance(session, int):
107            raise TypeError(session)
108
109        # API function declaration
110        self.library.GW2CloseSession.argtypes = [ct.c_size_t]
111
112        # Variable initialisation
113        ct_session = ct.c_size_t(session)
114
115        # API call
116        status = self.library.GW2CloseSession(ct_session)
117
118        if status not in successes.success_codes:
119            log.error(f"\n\tsession: {session}\n\tstatus: {status}")
120        else:
121            log.debug(f"\n\tsession: {session}\n\tstatus: {status}")
122
123        return status

Close the Glasswall session. All resources allocated by the session will be destroyed.

Args: session (int): The session to close.

Returns: status (int): The status code of the function call.

@contextmanager
def new_session(self):
125    @contextmanager
126    def new_session(self):
127        """ Context manager. Opens a new session on entry and closes the session on exit. """
128        try:
129            session = self.open_session()
130            yield session
131        finally:
132            self.close_session(session)

Context manager. Opens a new session on entry and closes the session on exit.

def run_session(self, session):
134    def run_session(self, session):
135        """ Runs the Glasswall session and begins processing of a file.
136
137        Args:
138            session (int): The session to run.
139
140        Returns:
141            status (int): The status code of the function call.
142        """
143        # API function declaration
144        self.library.GW2RunSession.argtypes = [ct.c_size_t]
145
146        # Variable initialisation
147        ct_session = ct.c_size_t(session)
148
149        # API call
150        status = self.library.GW2RunSession(ct_session)
151
152        if status not in successes.success_codes:
153            log.error(f"\n\tsession: {session}\n\tstatus: {status}\n\tGW2FileErrorMsg: {self.file_error_message(session)}")
154        else:
155            log.debug(f"\n\tsession: {session}\n\tstatus: {status}\n\tGW2FileErrorMsg: {self.file_error_message(session)}")
156
157        return status

Runs the Glasswall session and begins processing of a file.

Args: session (int): The session to run.

Returns: status (int): The status code of the function call.

def determine_file_type( self, input_file: str | bytes | bytearray | _io.BytesIO, as_string: bool = False, raise_unsupported: bool = True) -> int | str:
159    def determine_file_type(self, input_file: Union[str, bytes, bytearray, io.BytesIO], as_string: bool = False, raise_unsupported: bool = True) -> Union[int, str]:
160        """ Determine the file type of a given input file, either as an integer identifier or a string.
161
162        Args:
163            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file to analyse. It can be provided as a file path (str), bytes, bytearray, or a BytesIO object.
164            as_string (bool, optional): Return file type as string, eg: "bmp" instead of: 29. Defaults to False.
165            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
166
167        Returns:
168            file_type (Union[int, str]): The file type.
169        """
170        if isinstance(input_file, str):
171            if not os.path.isfile(input_file):
172                raise FileNotFoundError(input_file)
173
174            # convert to ct.c_char_p of bytes
175            ct_input_file = ct.c_char_p(input_file.encode("utf-8"))
176
177            # API call
178            file_type = self.library.GW2DetermineFileTypeFromFile(ct_input_file)
179
180        elif isinstance(input_file, (bytes, bytearray, io.BytesIO)):
181            # convert to bytes
182            bytes_input_file = utils.as_bytes(input_file)
183
184            # ctypes conversion
185            ct_buffer = ct.c_char_p(bytes_input_file)
186            ct_buffer_length = ct.c_size_t(len(bytes_input_file))
187
188            # API call
189            file_type = self.library.GW2DetermineFileTypeFromMemory(
190                ct_buffer,
191                ct_buffer_length
192            )
193
194        else:
195            raise TypeError(input_file)
196
197        file_type_as_string = dft.file_type_int_to_str(file_type)
198        input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
199
200        if not dft.is_success(file_type):
201            log.warning(f"\n\tfile_type: {file_type}\n\tfile_type_as_string: {file_type_as_string}\n\tinput_file: {input_file_repr}")
202            if raise_unsupported:
203                raise dft.int_class_map.get(file_type, dft.errors.UnknownErrorCode)(file_type)
204        else:
205            log.debug(f"\n\tfile_type: {file_type}\n\tfile_type_as_string: {file_type_as_string}\n\tinput_file: {input_file_repr}")
206
207        if as_string:
208            return file_type_as_string
209
210        return file_type

Determine the file type of a given input file, either as an integer identifier or a string.

Args: input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file to analyse. It can be provided as a file path (str), bytes, bytearray, or a BytesIO object. as_string (bool, optional): Return file type as string, eg: "bmp" instead of: 29. Defaults to False. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: file_type (Union[int, str]): The file type.

def get_content_management_policy(self, session: int) -> str:
255    def get_content_management_policy(self, session: int) -> str:
256        """ Returns the content management configuration for a given session by querying the engine.
257
258        Args:
259            session (int): The session integer.
260
261        Returns:
262            xml_string (str): The XML string of the current content management configuration.
263        """
264        gw_return_object = self._GW2GetPolicySettings(session)
265
266        if gw_return_object.status not in successes.success_codes:
267            log.error(
268                f"GW2GetPolicySettings failed with status: "
269                f"{gw_return_object.status}"
270            )
271            raise errors.error_codes.get(
272                gw_return_object.status, errors.UnknownErrorCode
273            )(gw_return_object.status)
274
275        log.debug(
276            f"GW2GetPolicySettings returned policy:\n"
277            f"{gw_return_object.policy}"
278        )
279
280        return gw_return_object.policy

Returns the content management configuration for a given session by querying the engine.

Args: session (int): The session integer.

Returns: xml_string (str): The XML string of the current content management configuration.

def set_content_management_policy( self, session: int, input_file: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, policy_format=0):
420    def set_content_management_policy(self, session: int, input_file: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None, policy_format=0):
421        """ Sets the content management policy configuration. If input_file is None then the engine's own default settings are applied unmodified.
422
423        Args:
424            session (int): The session integer.
425            input_file (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (engine defaults). The content management policy to apply.
426            policy_format (int): The format of the content management policy. 0=XML.
427
428        Returns:
429            - result (glasswall.GwReturnObj): Depending on the input 'input_file':
430                - If input_file is a str file path:
431                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'policy_format', 'status'.
432
433                - If input_file is a file in memory:
434                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'policy_format', 'status'.
435        """
436        # Validate type
437        if not isinstance(session, int):
438            raise TypeError(session)
439        if not isinstance(input_file, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
440            raise TypeError(input_file)
441        if not isinstance(policy_format, int):
442            raise TypeError(policy_format)
443
444        # Handle None: resolve against engine defaults without overriding any switch values
445        if input_file is None:
446            input_file = glasswall.content_management.policies.Editor(default=None)
447
448        # Handle Editor policy: resolve against engine defaults
449        if isinstance(input_file, glasswall.content_management.policies.Editor):
450            input_file = self._resolve_editor_policy(session, input_file)
451
452        # Validate xml content is parsable
453        utils.validate_xml(input_file)
454
455        # From file
456        if isinstance(input_file, str) and os.path.isfile(input_file):
457            input_file = os.path.abspath(input_file)
458
459            result = self._GW2RegisterPoliciesFile(session, input_file)
460
461        # From memory
462        elif isinstance(input_file, (str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
463            # Convert bytearray, io.BytesIO to bytes
464            if isinstance(input_file, (bytearray, io.BytesIO)):
465                input_file = utils.as_bytes(input_file)
466            # Convert string xml or Policy to bytes
467            if isinstance(input_file, (str, glasswall.content_management.policies.policy.Policy)):
468                input_file = input_file.encode("utf-8")
469
470            result = self._GW2RegisterPoliciesMemory(session, input_file)
471
472        if result.status not in successes.success_codes:
473            log.error(format_object(result))
474            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
475        else:
476            log.debug(format_object(result))
477
478        return result

Sets the content management policy configuration. If input_file is None then the engine's own default settings are applied unmodified.

Args: session (int): The session integer. input_file (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (engine defaults). The content management policy to apply. policy_format (int): The format of the content management policy. 0=XML.

Returns: - result (glasswall.GwReturnObj): Depending on the input 'input_file': - If input_file is a str file path: - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'policy_format', 'status'.

    - If input_file is a file in memory:
        - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'policy_format', 'status'.
def register_input( self, session: int, input_file: str | bytes | bytearray | _io.BytesIO):
542    def register_input(self, session: int, input_file: Union[str, bytes, bytearray, io.BytesIO]):
543        """ Register an input file or bytes for the given session.
544
545        Args:
546            session (int): The session integer.
547            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
548
549        Returns:
550            - result (glasswall.GwReturnObj): Depending on the input 'input_file':
551                - If input_file is a str file path:
552                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'status'.
553
554                - If input_file is a file in memory:
555                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
556        """
557        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO,)):
558            raise TypeError(input_file)
559
560        if isinstance(input_file, str):
561            if not os.path.isfile(input_file):
562                raise FileNotFoundError(input_file)
563
564            input_file = os.path.abspath(input_file)
565
566            result = self._GW2RegisterInputFile(session, input_file)
567
568        elif isinstance(input_file, (bytes, bytearray, io.BytesIO,)):
569            # Convert bytearray and io.BytesIO to bytes
570            input_file = utils.as_bytes(input_file)
571
572            result = self._GW2RegisterInputMemory(session, input_file)
573
574        if result.status not in successes.success_codes:
575            log.error(format_object(result))
576            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
577        else:
578            log.debug(format_object(result))
579
580        return result

Register an input file or bytes for the given session.

Args: session (int): The session integer. input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.

Returns: - result (glasswall.GwReturnObj): Depending on the input 'input_file': - If input_file is a str file path: - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'status'.

    - If input_file is a file in memory:
        - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
def register_output(self, session, output_file: str | None = None):
642    def register_output(self, session, output_file: Optional[str] = None):
643        """ Register an output file for the given session. If output_file is None the file will be returned as 'buffer' and 'buffer_length' attributes.
644
645        Args:
646            session (int): The session integer.
647            output_file (Optional[str]): If specified, during run session the file will be written to output_file, otherwise the file will be written to the glasswall.GwReturnObj 'buffer' and 'buffer_length' attributes.
648
649        Returns:
650            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attribute 'status' indicating the result of the function call. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size.
651        """
652        if not isinstance(output_file, (type(None), str)):
653            raise TypeError(output_file)
654
655        if isinstance(output_file, str):
656            output_file = os.path.abspath(output_file)
657
658            result = self._GW2RegisterOutFile(session, output_file)
659
660        elif isinstance(output_file, type(None)):
661            result = self._GW2RegisterOutputMemory(session)
662
663        if result.status not in successes.success_codes:
664            log.error(format_object(result))
665            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
666        else:
667            log.debug(format_object(result))
668
669        return result

Register an output file for the given session. If output_file is None the file will be returned as 'buffer' and 'buffer_length' attributes.

Args: session (int): The session integer. output_file (Optional[str]): If specified, during run session the file will be written to output_file, otherwise the file will be written to the glasswall.GwReturnObj 'buffer' and 'buffer_length' attributes.

Returns: gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attribute 'status' indicating the result of the function call. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size.

def register_analysis(self, session: int, output_file: str | None = None):
739    def register_analysis(self, session: int, output_file: Optional[str] = None):
740        """ Registers an analysis file for the given session. The analysis file will be created during the session's run_session call.
741
742        Args:
743            session (int): The session integer.
744            output_file (Optional[str]): Default None. The file path where the analysis will be written. None returns the analysis as bytes.
745
746        Returns:
747            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'status', 'session', 'analysis_format'. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size. If output_file is not None (file mode) 'output_file' is included.
748        """
749        if not isinstance(output_file, (type(None), str)):
750            raise TypeError(output_file)
751
752        if isinstance(output_file, str):
753            output_file = os.path.abspath(output_file)
754
755            result = self._GW2RegisterAnalysisFile(session, output_file)
756
757        elif isinstance(output_file, type(None)):
758            result = self._GW2RegisterAnalysisMemory(session)
759
760        if result.status not in successes.success_codes:
761            log.error(format_object(result))
762            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
763        else:
764            log.debug(format_object(result))
765
766        return result

Registers an analysis file for the given session. The analysis file will be created during the session's run_session call.

Args: session (int): The session integer. output_file (Optional[str]): Default None. The file path where the analysis will be written. None returns the analysis as bytes.

Returns: gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'status', 'session', 'analysis_format'. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size. If output_file is not None (file mode) 'output_file' is included.

def protect_file( self, input_file: str | bytes | bytearray | _io.BytesIO, output_file: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
768    def protect_file(
769        self,
770        input_file: Union[str, bytes, bytearray, io.BytesIO],
771        output_file: Optional[str] = None,
772        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
773        raise_unsupported: bool = True,
774    ):
775        """ Protects a file using the current content management configuration, returning the file bytes. The protected file is written to output_file if it is provided.
776
777        Args:
778            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
779            output_file (Optional[str]): The output file path where the protected file will be written.
780            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
781            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
782
783        Returns:
784            file_bytes (bytes): The protected file bytes.
785        """
786        # Validate arg types
787        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
788            raise TypeError(input_file)
789        if not isinstance(output_file, (type(None), str)):
790            raise TypeError(output_file)
791        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
792            raise TypeError(content_management_policy)
793        if not isinstance(raise_unsupported, bool):
794            raise TypeError(raise_unsupported)
795
796        # Convert string path arguments to absolute paths
797        if isinstance(input_file, str):
798            if not os.path.isfile(input_file):
799                raise FileNotFoundError(input_file)
800            input_file = os.path.abspath(input_file)
801        if isinstance(output_file, str):
802            output_file = os.path.abspath(output_file)
803            # make directories that do not exist
804            os.makedirs(os.path.dirname(output_file), exist_ok=True)
805        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
806            content_management_policy = os.path.abspath(content_management_policy)
807
808        # Convert memory inputs to bytes
809        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
810            input_file = utils.as_bytes(input_file)
811
812        with utils.CwdHandler(self.library_path):
813            with self.new_session() as session:
814                content_management_policy = self.set_content_management_policy(session, content_management_policy)
815                register_input = self.register_input(session, input_file)
816                register_output = self.register_output(session, output_file=output_file)
817                status = self.run_session(session)
818
819                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
820                if status not in successes.success_codes:
821                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
822                    if raise_unsupported:
823                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
824                    else:
825                        file_bytes = None
826                else:
827                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
828                    # Get file bytes
829                    if isinstance(output_file, str):
830                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
831                        if not os.path.isfile(output_file):
832                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
833                            file_bytes = None
834                        else:
835                            with open(output_file, "rb") as f:
836                                file_bytes = f.read()
837                    else:
838                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
839                        file_bytes = utils.buffer_to_bytes(
840                            register_output.buffer,
841                            register_output.buffer_length
842                        )
843
844                # Ensure memory allocated is not garbage collected
845                content_management_policy, register_input, register_output
846
847                return file_bytes

Protects a file using the current content management configuration, returning the file bytes. The protected file is written to output_file if it is provided.

Args: input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes. output_file (Optional[str]): The output file path where the protected file will be written. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: file_bytes (bytes): The protected file bytes.

def protect_directory( self, input_directory: str, output_directory: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
849    def protect_directory(
850        self,
851        input_directory: str,
852        output_directory: Optional[str] = None,
853        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
854        raise_unsupported: bool = True,
855    ):
856        """ Recursively processes all files in a directory in protect mode using the given content management policy.
857        The protected files are written to output_directory maintaining the same directory structure as input_directory.
858
859        Args:
860            input_directory (str): The input directory containing files to protect.
861            output_directory (Optional[str]): The output directory where the protected file will be written, or None to not write files.
862            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply.
863            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
864
865        Returns:
866            protected_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.
867        """
868        protected_files_dict = {}
869        # Call protect_file on each file in input_directory to output_directory
870        for input_file in utils.list_file_paths(input_directory):
871            relative_path = os.path.relpath(input_file, input_directory)
872            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
873
874            protected_bytes = self.protect_file(
875                input_file=input_file,
876                output_file=output_file,
877                raise_unsupported=raise_unsupported,
878                content_management_policy=content_management_policy,
879            )
880
881            protected_files_dict[relative_path] = protected_bytes
882
883        return protected_files_dict

Recursively processes all files in a directory in protect mode using the given content management policy. The protected files are written to output_directory maintaining the same directory structure as input_directory.

Args: input_directory (str): The input directory containing files to protect. output_directory (Optional[str]): The output directory where the protected file will be written, or None to not write files. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: protected_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.

def analyse_file( self, input_file: str | bytes | bytearray | _io.BytesIO, output_file: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
885    def analyse_file(
886        self,
887        input_file: Union[str, bytes, bytearray, io.BytesIO],
888        output_file: Optional[str] = None,
889        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
890        raise_unsupported: bool = True,
891    ):
892        """ Analyses a file, returning the analysis bytes. The analysis is written to output_file if it is provided.
893
894        Args:
895            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
896            output_file (Optional[str]): The output file path where the analysis file will be written.
897            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
898            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
899
900        Returns:
901            file_bytes (bytes): The analysis file bytes.
902        """
903        # Validate arg types
904        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
905            raise TypeError(input_file)
906        if not isinstance(output_file, (type(None), str)):
907            raise TypeError(output_file)
908        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
909            raise TypeError(content_management_policy)
910        if not isinstance(raise_unsupported, bool):
911            raise TypeError(raise_unsupported)
912
913        # Convert string path arguments to absolute paths
914        if isinstance(input_file, str):
915            if not os.path.isfile(input_file):
916                raise FileNotFoundError(input_file)
917            input_file = os.path.abspath(input_file)
918        if isinstance(output_file, str):
919            output_file = os.path.abspath(output_file)
920            # make directories that do not exist
921            os.makedirs(os.path.dirname(output_file), exist_ok=True)
922        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
923            content_management_policy = os.path.abspath(content_management_policy)
924
925        # Convert memory inputs to bytes
926        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
927            input_file = utils.as_bytes(input_file)
928
929        with utils.CwdHandler(self.library_path):
930            with self.new_session() as session:
931                content_management_policy = self.set_content_management_policy(session, content_management_policy)
932                register_input = self.register_input(session, input_file)
933                register_analysis = self.register_analysis(session, output_file)
934                status = self.run_session(session)
935
936                file_bytes = None
937                if isinstance(output_file, str):
938                    # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
939                    if os.path.isfile(output_file):
940                        with open(output_file, "rb") as f:
941                            file_bytes = f.read()
942                else:
943                    # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
944                    if register_analysis.buffer and register_analysis.buffer_length:
945                        file_bytes = utils.buffer_to_bytes(
946                            register_analysis.buffer,
947                            register_analysis.buffer_length
948                        )
949
950                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
951                if status not in successes.success_codes:
952                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
953                    if raise_unsupported:
954                        error = errors.error_codes.get(status, errors.UnknownErrorCode)(status)
955                        error.analysis_bytes = file_bytes
956                        raise error
957                else:
958                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
959
960                # Ensure memory allocated is not garbage collected
961                content_management_policy, register_input, register_analysis
962
963                return file_bytes

Analyses a file, returning the analysis bytes. The analysis is written to output_file if it is provided.

Args: input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes. output_file (Optional[str]): The output file path where the analysis file will be written. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: file_bytes (bytes): The analysis file bytes.

def analyse_directory( self, input_directory: str, output_directory: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
965    def analyse_directory(
966        self,
967        input_directory: str,
968        output_directory: Optional[str] = None,
969        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
970        raise_unsupported: bool = True,
971    ):
972        """ Analyses all files in a directory and its subdirectories. The analysis files are written to output_directory maintaining the same directory structure as input_directory.
973
974        Args:
975            input_directory (str): The input directory containing files to analyse.
976            output_directory (Optional[str]): The output directory where the analysis files will be written, or None to not write files.
977            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply.
978            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
979
980        Returns:
981            analysis_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.
982        """
983        analysis_files_dict = {}
984        # Call analyse_file on each file in input_directory to output_directory
985        for input_file in utils.list_file_paths(input_directory):
986            relative_path = os.path.relpath(input_file, input_directory) + ".xml"
987            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
988
989            analysis_bytes = self.analyse_file(
990                input_file=input_file,
991                output_file=output_file,
992                raise_unsupported=raise_unsupported,
993                content_management_policy=content_management_policy,
994            )
995
996            analysis_files_dict[relative_path] = analysis_bytes
997
998        return analysis_files_dict

Analyses all files in a directory and its subdirectories. The analysis files are written to output_directory maintaining the same directory structure as input_directory.

Args: input_directory (str): The input directory containing files to analyse. output_directory (Optional[str]): The output directory where the analysis files will be written, or None to not write files. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: analysis_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.

def protect_and_analyse_file( self, input_file: str | bytes | bytearray | _io.BytesIO, output_file: str | None = None, output_analysis_report: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
1000    def protect_and_analyse_file(
1001        self,
1002        input_file: Union[str, bytes, bytearray, io.BytesIO],
1003        output_file: Optional[str] = None,
1004        output_analysis_report: Optional[str] = None,
1005        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1006        raise_unsupported: bool = True,
1007    ):
1008        """ Protects and analyses a file in a single session, returning both protected file bytes and analysis report bytes.
1009
1010        Args:
1011            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
1012            output_file (Optional[str]): The output file path where the protected file will be written.
1013            output_analysis_report (Optional[str]): The output file path where the XML analysis report will be written.
1014            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1015            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1016
1017        Returns:
1018            Tuple[Optional[bytes], Optional[bytes]]: A tuple of (protected_file_bytes, analysis_report_bytes).
1019        """
1020        # Validate arg types
1021        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1022            raise TypeError(f"input_file expected to be of type: str, bytes, bytearray, io.BytesIO. Got: {type(input_file).__name__}")
1023        if not isinstance(output_file, (type(None), str)):
1024            raise TypeError(f"output_file expected to be of type: None, str. Got: {type(output_file).__name__}")
1025        if not isinstance(output_analysis_report, (type(None), str)):
1026            raise TypeError(f"output_analysis_report expected to be of type: None, str. Got: {type(output_analysis_report).__name__}")
1027        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1028            raise TypeError(f"content_management_policy expected to be of type: None, str, bytes, bytearray, io.BytesIO, Policy. Got: {type(content_management_policy).__name__}")
1029        if not isinstance(raise_unsupported, bool):
1030            raise TypeError(f"raise_unsupported expected to be of type: bool. Got: {type(raise_unsupported).__name__}")
1031
1032        # Convert string path arguments to absolute paths
1033        if isinstance(input_file, str):
1034            if not os.path.isfile(input_file):
1035                raise FileNotFoundError(input_file)
1036            input_file = os.path.abspath(input_file)
1037        if isinstance(output_file, str):
1038            output_file = os.path.abspath(output_file)
1039            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1040        if isinstance(output_analysis_report, str):
1041            output_analysis_report = os.path.abspath(output_analysis_report)
1042            os.makedirs(os.path.dirname(output_analysis_report), exist_ok=True)
1043        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1044            content_management_policy = os.path.abspath(content_management_policy)
1045
1046        # Convert memory inputs to bytes
1047        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1048            input_file = utils.as_bytes(input_file)
1049
1050        with utils.CwdHandler(self.library_path):
1051            with self.new_session() as session:
1052                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1053                register_input = self.register_input(session, input_file)
1054                register_output = self.register_output(session, output_file)
1055                register_analysis = self.register_analysis(session, output_analysis_report)
1056
1057                status = self.run_session(session)
1058
1059                protected_file_bytes = None
1060                analysis_report_bytes = None
1061
1062                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1063                if status not in successes.success_codes:
1064                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1065                    if raise_unsupported:
1066                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1067
1068                # Get analysis report file bytes, even on processing failure
1069                if isinstance(output_analysis_report, str):
1070                    # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1071                    if not os.path.isfile(output_analysis_report):
1072                        log.error(f"Editor returned success code: {status} but no output file was found: {output_analysis_report}")
1073                    else:
1074                        with open(output_analysis_report, "rb") as f:
1075                            analysis_report_bytes = f.read()
1076                else:
1077                    # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1078                    if register_analysis.buffer and register_analysis.buffer_length:
1079                        analysis_report_bytes = utils.buffer_to_bytes(
1080                            register_analysis.buffer,
1081                            register_analysis.buffer_length
1082                        )
1083
1084                # On success, get protected file bytes
1085                if status in successes.success_codes:
1086                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1087                    # Get file bytes
1088                    if isinstance(output_file, str):
1089                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1090                        if not os.path.isfile(output_file):
1091                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1092                        else:
1093                            with open(output_file, "rb") as f:
1094                                protected_file_bytes = f.read()
1095                    else:
1096                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1097                        protected_file_bytes = utils.buffer_to_bytes(
1098                            register_output.buffer,
1099                            register_output.buffer_length
1100                        )
1101
1102                # Ensure memory allocated is not garbage collected
1103                content_management_policy, register_input, register_output, register_analysis
1104
1105                return protected_file_bytes, analysis_report_bytes

Protects and analyses a file in a single session, returning both protected file bytes and analysis report bytes.

Args: input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes. output_file (Optional[str]): The output file path where the protected file will be written. output_analysis_report (Optional[str]): The output file path where the XML analysis report will be written. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: Tuple[Optional[bytes], Optional[bytes]]: A tuple of (protected_file_bytes, analysis_report_bytes).

def create_config( self, default: str | None = None, config: dict = {}) -> glasswall.content_management.policies.policy.Policy:
1107    def create_config(self, default: Optional[str] = None, config: dict = {}) -> "glasswall.content_management.policies.policy.Policy":
1108        """ Creates a content management policy by querying the engine's default settings and
1109        applying the specified default and any explicit switch overrides on top.
1110
1111        Opens a temporary session solely to retrieve the engine's default policy via
1112        GW2GetPolicySettings, resolves the Editor policy against those defaults, then
1113        closes the session. No file processing is performed.
1114
1115        Args:
1116            default (Optional[str]): Default None. The value to apply to all content switches,
1117                overriding the engine defaults. (allow, disallow, sanitise). If None, the
1118                engine's own default values are returned unmodified.
1119            config (dict): A dictionary of explicit switch overrides. Keys are config element
1120                names (e.g. "pdfConfig"), values are dicts of switch name to switch value.
1121                Attribute overrides use the "@" prefix (e.g. "@libVersion").
1122
1123        Returns:
1124            policy (glasswall.content_management.policies.policy.Policy): A fully resolved
1125                Policy object. Calling str() or print() on it returns the XML representation.
1126
1127        Example:
1128            >>> print(editor.create_config())
1129            >>> print(editor.create_config(default="sanitise"))
1130            >>> print(editor.create_config(default="allow", config={"pdfConfig": {"metadata": "sanitise"}}))
1131        """
1132        editor_policy = glasswall.content_management.policies.Editor(default=default, config=config)
1133        with self.new_session() as session:
1134            return self._resolve_editor_policy(session, editor_policy)

Creates a content management policy by querying the engine's default settings and applying the specified default and any explicit switch overrides on top.

Opens a temporary session solely to retrieve the engine's default policy via GW2GetPolicySettings, resolves the Editor policy against those defaults, then closes the session. No file processing is performed.

Args: default (Optional[str]): Default None. The value to apply to all content switches, overriding the engine defaults. (allow, disallow, sanitise). If None, the engine's own default values are returned unmodified. config (dict): A dictionary of explicit switch overrides. Keys are config element names (e.g. "pdfConfig"), values are dicts of switch name to switch value. Attribute overrides use the "@" prefix (e.g. "@libVersion").

Returns: policy (glasswall.content_management.policies.policy.Policy): A fully resolved Policy object. Calling str() or print() on it returns the XML representation.

Example:

print(editor.create_config()) print(editor.create_config(default="sanitise")) print(editor.create_config(default="allow", config={"pdfConfig": {"metadata": "sanitise"}}))

def protect_and_analyse_directory( self, input_directory: str, output_directory: str | None = None, analysis_directory: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
1136    def protect_and_analyse_directory(
1137        self,
1138        input_directory: str,
1139        output_directory: Optional[str] = None,
1140        analysis_directory: Optional[str] = None,
1141        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1142        raise_unsupported: bool = True,
1143    ):
1144        """ Recursively processes all files in a directory using protect and analyse mode with the given content management policy.
1145        Outputs are written to output_directory and analysis_directory maintaining the same structure as input_directory.
1146
1147        Args:
1148            input_directory (str): The input directory containing files to process.
1149            output_directory (Optional[str]): The output directory for protected files.
1150            analysis_directory (Optional[str]): The output directory for XML analysis reports.
1151            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply.
1152            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1153
1154        Returns:
1155            result_dict (dict): A dictionary mapping relative file paths to tuples of (protected_file_bytes, analysis_report_bytes).
1156        """
1157        result_dict = {}
1158        for input_file in utils.list_file_paths(input_directory):
1159            relative_path = os.path.relpath(input_file, input_directory)
1160            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
1161            output_analysis_report = None if analysis_directory is None else os.path.join(os.path.abspath(analysis_directory), relative_path + ".xml")
1162
1163            protected_file_bytes, analysis_report_bytes = self.protect_and_analyse_file(
1164                input_file=input_file,
1165                output_file=output_file,
1166                output_analysis_report=output_analysis_report,
1167                content_management_policy=content_management_policy,
1168                raise_unsupported=raise_unsupported,
1169            )
1170
1171            result_dict[relative_path] = (protected_file_bytes, analysis_report_bytes)
1172
1173        return result_dict

Recursively processes all files in a directory using protect and analyse mode with the given content management policy. Outputs are written to output_directory and analysis_directory maintaining the same structure as input_directory.

Args: input_directory (str): The input directory containing files to process. output_directory (Optional[str]): The output directory for protected files. analysis_directory (Optional[str]): The output directory for XML analysis reports. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: result_dict (dict): A dictionary mapping relative file paths to tuples of (protected_file_bytes, analysis_report_bytes).

def register_export(self, session: int, output_file: str | None = None):
1235    def register_export(self, session: int, output_file: Optional[str] = None):
1236        """ Registers a file to be exported for the given session. The export file will be created during the session's run_session call.
1237
1238        Args:
1239            session (int): The session integer.
1240            output_file (Optional[str]): Default None. The file path where the export will be written. None exports the file in memory.
1241
1242        Returns:
1243            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attribute 'status' indicating the result of the function call and 'session', the session integer. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size.
1244        """
1245        if not isinstance(output_file, (type(None), str)):
1246            raise TypeError(output_file)
1247
1248        if isinstance(output_file, str):
1249            output_file = os.path.abspath(output_file)
1250
1251            result = self._GW2RegisterExportFile(session, output_file)
1252
1253        elif isinstance(output_file, type(None)):
1254            result = self._GW2RegisterExportMemory(session)
1255
1256        if result.status not in successes.success_codes:
1257            log.error(format_object(result))
1258            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
1259        else:
1260            log.debug(format_object(result))
1261
1262        return result

Registers a file to be exported for the given session. The export file will be created during the session's run_session call.

Args: session (int): The session integer. output_file (Optional[str]): Default None. The file path where the export will be written. None exports the file in memory.

Returns: gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attribute 'status' indicating the result of the function call and 'session', the session integer. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size.

def export_file( self, input_file: str | bytes | bytearray | _io.BytesIO, output_file: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
1264    def export_file(
1265        self,
1266        input_file: Union[str, bytes, bytearray, io.BytesIO],
1267        output_file: Optional[str] = None,
1268        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1269        raise_unsupported: bool = True,
1270    ):
1271        """ Export a file, returning the .zip file bytes. The .zip file is written to output_file if it is provided.
1272
1273        Args:
1274            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
1275            output_file (Optional[str]): The output file path where the .zip file will be written.
1276            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1277            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1278
1279        Returns:
1280            file_bytes (bytes): The exported .zip file.
1281        """
1282        # Validate arg types
1283        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1284            raise TypeError(input_file)
1285        if not isinstance(output_file, (type(None), str)):
1286            raise TypeError(output_file)
1287        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1288            raise TypeError(content_management_policy)
1289        if not isinstance(raise_unsupported, bool):
1290            raise TypeError(raise_unsupported)
1291
1292        # Convert string path arguments to absolute paths
1293        if isinstance(input_file, str):
1294            if not os.path.isfile(input_file):
1295                raise FileNotFoundError(input_file)
1296            input_file = os.path.abspath(input_file)
1297        if isinstance(output_file, str):
1298            output_file = os.path.abspath(output_file)
1299            # make directories that do not exist
1300            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1301        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1302            content_management_policy = os.path.abspath(content_management_policy)
1303
1304        # Convert memory inputs to bytes
1305        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1306            input_file = utils.as_bytes(input_file)
1307
1308        with utils.CwdHandler(self.library_path):
1309            with self.new_session() as session:
1310                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1311                register_input = self.register_input(session, input_file)
1312                register_export = self.register_export(session, output_file)
1313                status = self.run_session(session)
1314
1315                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1316                if status not in successes.success_codes:
1317                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
1318                    if raise_unsupported:
1319                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1320                    else:
1321                        file_bytes = None
1322                else:
1323                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
1324                    # Get file bytes
1325                    if isinstance(output_file, str):
1326                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1327                        if not os.path.isfile(output_file):
1328                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1329                            file_bytes = None
1330                        else:
1331                            with open(output_file, "rb") as f:
1332                                file_bytes = f.read()
1333                    else:
1334                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1335                        file_bytes = utils.buffer_to_bytes(
1336                            register_export.buffer,
1337                            register_export.buffer_length
1338                        )
1339
1340                # Ensure memory allocated is not garbage collected
1341                content_management_policy, register_input, register_export
1342
1343                return file_bytes

Export a file, returning the .zip file bytes. The .zip file is written to output_file if it is provided.

Args: input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes. output_file (Optional[str]): The output file path where the .zip file will be written. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: file_bytes (bytes): The exported .zip file.

def export_directory( self, input_directory: str, output_directory: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
1345    def export_directory(
1346        self,
1347        input_directory: str,
1348        output_directory: Optional[str] = None,
1349        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1350        raise_unsupported: bool = True
1351    ):
1352        """ Exports all files in a directory and its subdirectories. The export files are written to output_directory maintaining the same directory structure as input_directory.
1353
1354        Args:
1355            input_directory (str): The input directory containing files to export.
1356            output_directory (Optional[str]): The output directory where the export files will be written, or None to not write files.
1357            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply.
1358            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1359
1360        Returns:
1361            export_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.
1362        """
1363        export_files_dict = {}
1364        # Call export_file on each file in input_directory to output_directory
1365        for input_file in utils.list_file_paths(input_directory):
1366            relative_path = os.path.relpath(input_file, input_directory) + ".zip"
1367            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
1368
1369            export_bytes = self.export_file(
1370                input_file=input_file,
1371                output_file=output_file,
1372                raise_unsupported=raise_unsupported,
1373                content_management_policy=content_management_policy,
1374            )
1375
1376            export_files_dict[relative_path] = export_bytes
1377
1378        return export_files_dict

Exports all files in a directory and its subdirectories. The export files are written to output_directory maintaining the same directory structure as input_directory.

Args: input_directory (str): The input directory containing files to export. output_directory (Optional[str]): The output directory where the export files will be written, or None to not write files. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: export_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.

def export_and_analyse_file( self, input_file: str | bytes | bytearray | _io.BytesIO, output_file: str | None = None, output_analysis_report: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
1380    def export_and_analyse_file(
1381        self,
1382        input_file: Union[str, bytes, bytearray, io.BytesIO],
1383        output_file: Optional[str] = None,
1384        output_analysis_report: Optional[str] = None,
1385        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1386        raise_unsupported: bool = True,
1387    ):
1388        """ Exports and analyses a file in a single session, returning both exported .zip bytes and analysis report bytes.
1389
1390        Args:
1391            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
1392            output_file (Optional[str]): The output file path where the .zip export will be written.
1393            output_analysis_report (Optional[str]): The output file path where the XML analysis report will be written.
1394            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1395            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1396
1397        Returns:
1398            Tuple[Optional[bytes], Optional[bytes]]: A tuple of (export_file_bytes, analysis_report_bytes).
1399        """
1400        # Validate arg types
1401        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1402            raise TypeError(f"input_file expected to be of type: str, bytes, bytearray, io.BytesIO. Got: {type(input_file).__name__}")
1403        if not isinstance(output_file, (type(None), str)):
1404            raise TypeError(f"output_file expected to be of type: None, str. Got: {type(output_file).__name__}")
1405        if not isinstance(output_analysis_report, (type(None), str)):
1406            raise TypeError(f"output_analysis_report expected to be of type: None, str. Got: {type(output_analysis_report).__name__}")
1407        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1408            raise TypeError(f"content_management_policy expected to be of type: None, str, bytes, bytearray, io.BytesIO, Policy. Got: {type(content_management_policy).__name__}")
1409        if not isinstance(raise_unsupported, bool):
1410            raise TypeError(f"raise_unsupported expected to be of type: bool. Got: {type(raise_unsupported).__name__}")
1411
1412        # Convert string path arguments to absolute paths
1413        if isinstance(input_file, str):
1414            if not os.path.isfile(input_file):
1415                raise FileNotFoundError(input_file)
1416            input_file = os.path.abspath(input_file)
1417        if isinstance(output_file, str):
1418            output_file = os.path.abspath(output_file)
1419            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1420        if isinstance(output_analysis_report, str):
1421            output_analysis_report = os.path.abspath(output_analysis_report)
1422            os.makedirs(os.path.dirname(output_analysis_report), exist_ok=True)
1423        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1424            content_management_policy = os.path.abspath(content_management_policy)
1425
1426        # Convert memory inputs to bytes
1427        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1428            input_file = utils.as_bytes(input_file)
1429
1430        with utils.CwdHandler(self.library_path):
1431            with self.new_session() as session:
1432                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1433                register_input = self.register_input(session, input_file)
1434                register_export = self.register_export(session, output_file)
1435                register_analysis = self.register_analysis(session, output_analysis_report)
1436
1437                status = self.run_session(session)
1438
1439                export_file_bytes = None
1440                analysis_report_bytes = None
1441
1442                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1443                if status not in successes.success_codes:
1444                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1445                    if raise_unsupported:
1446                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1447
1448                # Get analysis report file bytes, even on processing failure
1449                if isinstance(output_analysis_report, str):
1450                    # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1451                    if not os.path.isfile(output_analysis_report):
1452                        log.error(f"Editor returned success code: {status} but no output file was found: {output_analysis_report}")
1453                    else:
1454                        with open(output_analysis_report, "rb") as f:
1455                            analysis_report_bytes = f.read()
1456                else:
1457                    # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1458                    if register_analysis.buffer and register_analysis.buffer_length:
1459                        analysis_report_bytes = utils.buffer_to_bytes(
1460                            register_analysis.buffer,
1461                            register_analysis.buffer_length
1462                        )
1463
1464                # On success, get export file bytes
1465                if status in successes.success_codes:
1466                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1467                    # Get export file bytes
1468                    if isinstance(output_file, str):
1469                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1470                        if not os.path.isfile(output_file):
1471                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1472                        else:
1473                            with open(output_file, "rb") as f:
1474                                export_file_bytes = f.read()
1475                    else:
1476                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1477                        export_file_bytes = utils.buffer_to_bytes(
1478                            register_export.buffer,
1479                            register_export.buffer_length
1480                        )
1481
1482                # Ensure memory allocated is not garbage collected
1483                content_management_policy, register_input, register_export, register_analysis
1484
1485                return export_file_bytes, analysis_report_bytes

Exports and analyses a file in a single session, returning both exported .zip bytes and analysis report bytes.

Args: input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes. output_file (Optional[str]): The output file path where the .zip export will be written. output_analysis_report (Optional[str]): The output file path where the XML analysis report will be written. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: Tuple[Optional[bytes], Optional[bytes]]: A tuple of (export_file_bytes, analysis_report_bytes).

def export_and_analyse_directory( self, input_directory: str, output_directory: str | None = None, analysis_directory: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
1487    def export_and_analyse_directory(
1488        self,
1489        input_directory: str,
1490        output_directory: Optional[str] = None,
1491        analysis_directory: Optional[str] = None,
1492        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1493        raise_unsupported: bool = True,
1494    ):
1495        """ Recursively processes all files in a directory using export and analyse mode with the given content management policy.
1496        Outputs are written to output_directory and analysis_directory maintaining the same structure as input_directory.
1497
1498        Args:
1499            input_directory (str): The input directory containing files to process.
1500            output_directory (Optional[str]): The output directory for exported .zip files.
1501            analysis_directory (Optional[str]): The output directory for XML analysis reports.
1502            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply.
1503            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1504
1505        Returns:
1506            result_dict (dict): A dictionary mapping relative file paths to tuples of (export_file_bytes, analysis_report_bytes).
1507        """
1508        result_dict = {}
1509
1510        for input_file in utils.list_file_paths(input_directory):
1511            relative_path = os.path.relpath(input_file, input_directory)
1512            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path + ".zip")
1513            output_analysis_report = None if analysis_directory is None else os.path.join(os.path.abspath(analysis_directory), relative_path + ".xml")
1514
1515            export_file_bytes, analysis_report_bytes = self.export_and_analyse_file(
1516                input_file=input_file,
1517                output_file=output_file,
1518                output_analysis_report=output_analysis_report,
1519                content_management_policy=content_management_policy,
1520                raise_unsupported=raise_unsupported,
1521            )
1522
1523            result_dict[relative_path] = (export_file_bytes, analysis_report_bytes)
1524
1525        return result_dict

Recursively processes all files in a directory using export and analyse mode with the given content management policy. Outputs are written to output_directory and analysis_directory maintaining the same structure as input_directory.

Args: input_directory (str): The input directory containing files to process. output_directory (Optional[str]): The output directory for exported .zip files. analysis_directory (Optional[str]): The output directory for XML analysis reports. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: result_dict (dict): A dictionary mapping relative file paths to tuples of (export_file_bytes, analysis_report_bytes).

def register_import( self, session: int, input_file: str | bytes | bytearray | _io.BytesIO):
1588    def register_import(self, session: int, input_file: Union[str, bytes, bytearray, io.BytesIO]):
1589        """ Registers a .zip file to be imported for the given session. The constructed file will be created during the session's run_session call.
1590
1591        Args:
1592            session (int): The session integer.
1593            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input import file path or bytes.
1594
1595        Returns:
1596            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attribute 'status' indicating the result of the function call. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size.
1597        """
1598        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO,)):
1599            raise TypeError(input_file)
1600
1601        if isinstance(input_file, str):
1602            if not os.path.isfile(input_file):
1603                raise FileNotFoundError(input_file)
1604
1605            input_file = os.path.abspath(input_file)
1606
1607            result = self._GW2RegisterImportFile(session, input_file)
1608
1609        elif isinstance(input_file, (bytes, bytearray, io.BytesIO,)):
1610            # Convert bytearray and io.BytesIO to bytes
1611            input_file = utils.as_bytes(input_file)
1612
1613            result = self._GW2RegisterImportMemory(session, input_file)
1614
1615        if result.status not in successes.success_codes:
1616            log.error(format_object(result))
1617            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
1618        else:
1619            log.debug(format_object(result))
1620
1621        return result

Registers a .zip file to be imported for the given session. The constructed file will be created during the session's run_session call.

Args: session (int): The session integer. input_file (Union[str, bytes, bytearray, io.BytesIO]): The input import file path or bytes.

Returns: gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attribute 'status' indicating the result of the function call. If output_file is None (memory mode), 'buffer', and 'buffer_length' are included containing the file content and file size.

def import_file( self, input_file: str | bytes | bytearray | _io.BytesIO, output_file: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
1623    def import_file(
1624        self,
1625        input_file: Union[str, bytes, bytearray, io.BytesIO],
1626        output_file: Optional[str] = None,
1627        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1628        raise_unsupported: bool = True,
1629    ):
1630        """ Import a .zip file, constructs a file from the .zip file and returns the file bytes. The file is written to output_file if it is provided.
1631
1632        Args:
1633            input_file (Union[str, bytes, bytearray, io.BytesIO]): The .zip input file path or bytes.
1634            output_file (Optional[str]): The output file path where the constructed file will be written.
1635            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1636            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1637
1638        Returns:
1639            file_bytes (bytes): The imported file bytes.
1640        """
1641        # Validate arg types
1642        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1643            raise TypeError(input_file)
1644        if not isinstance(output_file, (type(None), str)):
1645            raise TypeError(output_file)
1646        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1647            raise TypeError(content_management_policy)
1648        if not isinstance(raise_unsupported, bool):
1649            raise TypeError(raise_unsupported)
1650
1651        # Convert string path arguments to absolute paths
1652        if isinstance(input_file, str):
1653            if not os.path.isfile(input_file):
1654                raise FileNotFoundError(input_file)
1655            input_file = os.path.abspath(input_file)
1656        if isinstance(output_file, str):
1657            output_file = os.path.abspath(output_file)
1658            # make directories that do not exist
1659            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1660        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1661            content_management_policy = os.path.abspath(content_management_policy)
1662
1663        # Convert memory inputs to bytes
1664        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1665            input_file = utils.as_bytes(input_file)
1666
1667        with utils.CwdHandler(self.library_path):
1668            with self.new_session() as session:
1669                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1670                register_import = self.register_import(session, input_file)
1671                register_output = self.register_output(session, output_file)
1672                status = self.run_session(session)
1673
1674                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray,)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1675                if status not in successes.success_codes:
1676                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
1677                    if raise_unsupported:
1678                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1679                    else:
1680                        file_bytes = None
1681                else:
1682                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\tsession: {session}\n\tstatus: {status}")
1683                    # Get file bytes
1684                    if isinstance(output_file, str):
1685                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1686                        if not os.path.isfile(output_file):
1687                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1688                            file_bytes = None
1689                        else:
1690                            with open(output_file, "rb") as f:
1691                                file_bytes = f.read()
1692                    else:
1693                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1694                        file_bytes = utils.buffer_to_bytes(
1695                            register_output.buffer,
1696                            register_output.buffer_length
1697                        )
1698
1699                # Ensure memory allocated is not garbage collected
1700                content_management_policy, register_import, register_output
1701
1702                return file_bytes

Import a .zip file, constructs a file from the .zip file and returns the file bytes. The file is written to output_file if it is provided.

Args: input_file (Union[str, bytes, bytearray, io.BytesIO]): The .zip input file path or bytes. output_file (Optional[str]): The output file path where the constructed file will be written. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: file_bytes (bytes): The imported file bytes.

def import_directory( self, input_directory: str, output_directory: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
1704    def import_directory(
1705        self,
1706        input_directory: str,
1707        output_directory: Optional[str] = None,
1708        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1709        raise_unsupported: bool = True,
1710    ):
1711        """ Imports all files in a directory and its subdirectories. Files are expected as .zip but this is not forced.
1712        The constructed files are written to output_directory maintaining the same directory structure as input_directory.
1713
1714        Args:
1715            input_directory (str): The input directory containing files to import.
1716            output_directory (Optional[str]): The output directory where the constructed files will be written, or None to not write files.
1717            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply.
1718            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1719
1720        Returns:
1721            import_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.
1722        """
1723        import_files_dict = {}
1724        # Call import_file on each file in input_directory to output_directory
1725        for input_file in utils.list_file_paths(input_directory):
1726            relative_path = os.path.relpath(input_file, input_directory)
1727            # Remove .zip extension from relative_path
1728            if relative_path.endswith(".zip"):
1729                relative_path = os.path.splitext(relative_path)[0]
1730            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
1731
1732            import_bytes = self.import_file(
1733                input_file=input_file,
1734                output_file=output_file,
1735                raise_unsupported=raise_unsupported,
1736                content_management_policy=content_management_policy,
1737            )
1738
1739            import_files_dict[relative_path] = import_bytes
1740
1741        return import_files_dict

Imports all files in a directory and its subdirectories. Files are expected as .zip but this is not forced. The constructed files are written to output_directory maintaining the same directory structure as input_directory.

Args: input_directory (str): The input directory containing files to import. output_directory (Optional[str]): The output directory where the constructed files will be written, or None to not write files. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): Default None (sanitise). The content management policy to apply. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: import_files_dict (dict): A dictionary of file paths relative to input_directory, and file bytes.

def import_and_analyse_file( self, input_file: str | bytes | bytearray | _io.BytesIO, output_file: str | None = None, output_analysis_report: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
1743    def import_and_analyse_file(
1744        self,
1745        input_file: Union[str, bytes, bytearray, io.BytesIO],
1746        output_file: Optional[str] = None,
1747        output_analysis_report: Optional[str] = None,
1748        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1749        raise_unsupported: bool = True,
1750    ):
1751        """ Imports and analyses a file in a single session, returning both imported file bytes and analysis report bytes.
1752
1753        Args:
1754            input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes.
1755            output_file (Optional[str]): The output file path where the imported file will be written.
1756            output_analysis_report (Optional[str]): The output file path where the XML analysis report will be written.
1757            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session.
1758            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1759
1760        Returns:
1761            Tuple[Optional[bytes], Optional[bytes]]: A tuple of (import_file_bytes, analysis_report_bytes).
1762        """
1763        # Validate arg types
1764        if not isinstance(input_file, (str, bytes, bytearray, io.BytesIO)):
1765            raise TypeError(f"input_file expected to be of type: str, bytes, bytearray, io.BytesIO. Got: {type(input_file).__name__}")
1766        if not isinstance(output_file, (type(None), str)):
1767            raise TypeError(f"output_file expected to be of type: None, str. Got: {type(output_file).__name__}")
1768        if not isinstance(output_analysis_report, (type(None), str)):
1769            raise TypeError(f"output_analysis_report expected to be of type: None, str. Got: {type(output_analysis_report).__name__}")
1770        if not isinstance(content_management_policy, (type(None), str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy)):
1771            raise TypeError(f"content_management_policy expected to be of type: None, str, bytes, bytearray, io.BytesIO, Policy. Got: {type(content_management_policy).__name__}")
1772        if not isinstance(raise_unsupported, bool):
1773            raise TypeError(f"raise_unsupported expected to be of type: bool. Got: {type(raise_unsupported).__name__}")
1774
1775        # Convert string path arguments to absolute paths
1776        if isinstance(input_file, str):
1777            if not os.path.isfile(input_file):
1778                raise FileNotFoundError(input_file)
1779            input_file = os.path.abspath(input_file)
1780        if isinstance(output_file, str):
1781            output_file = os.path.abspath(output_file)
1782            os.makedirs(os.path.dirname(output_file), exist_ok=True)
1783        if isinstance(output_analysis_report, str):
1784            output_analysis_report = os.path.abspath(output_analysis_report)
1785            os.makedirs(os.path.dirname(output_analysis_report), exist_ok=True)
1786        if isinstance(content_management_policy, str) and os.path.isfile(content_management_policy):
1787            content_management_policy = os.path.abspath(content_management_policy)
1788
1789        # Convert memory inputs to bytes
1790        if isinstance(input_file, (bytes, bytearray, io.BytesIO)):
1791            input_file = utils.as_bytes(input_file)
1792
1793        with utils.CwdHandler(self.library_path):
1794            with self.new_session() as session:
1795                content_management_policy = self.set_content_management_policy(session, content_management_policy)
1796                register_import = self.register_import(session, input_file)
1797                register_output = self.register_output(session, output_file)
1798                register_analysis = self.register_analysis(session, output_analysis_report)
1799
1800                status = self.run_session(session)
1801
1802                import_file_bytes = None
1803                analysis_report_bytes = None
1804
1805                input_file_repr = f"{type(input_file)} length {len(input_file)}" if isinstance(input_file, (bytes, bytearray)) else input_file.__sizeof__() if isinstance(input_file, io.BytesIO) else input_file
1806                if status not in successes.success_codes:
1807                    log.error(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1808                    if raise_unsupported:
1809                        raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1810
1811                # Get analysis report file bytes, even on processing failure
1812                if isinstance(output_analysis_report, str):
1813                    # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1814                    if not os.path.isfile(output_analysis_report):
1815                        log.error(f"Editor returned success code: {status} but no output file was found: {output_analysis_report}")
1816                    else:
1817                        with open(output_analysis_report, "rb") as f:
1818                            analysis_report_bytes = f.read()
1819                else:
1820                    # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1821                    if register_analysis.buffer and register_analysis.buffer_length:
1822                        analysis_report_bytes = utils.buffer_to_bytes(
1823                            register_analysis.buffer,
1824                            register_analysis.buffer_length
1825                        )
1826
1827                # On success, get import file bytes
1828                if status in successes.success_codes:
1829                    log.debug(f"\n\tinput_file: {input_file_repr}\n\toutput_file: {output_file}\n\toutput_analysis_report: {output_analysis_report}\n\tsession: {session}\n\tstatus: {status}")
1830                    # Get import file bytes
1831                    if isinstance(output_file, str):
1832                        # File to file and memory to file, Editor wrote to a file, read it to get the file bytes
1833                        if not os.path.isfile(output_file):
1834                            log.error(f"Editor returned success code: {status} but no output file was found: {output_file}")
1835                        else:
1836                            with open(output_file, "rb") as f:
1837                                import_file_bytes = f.read()
1838                    else:
1839                        # File to memory and memory to memory, Editor wrote to a buffer, convert it to bytes
1840                        import_file_bytes = utils.buffer_to_bytes(
1841                            register_output.buffer,
1842                            register_output.buffer_length
1843                        )
1844
1845                # Ensure memory allocated is not garbage collected
1846                content_management_policy, register_import, register_output, register_analysis
1847
1848                return import_file_bytes, analysis_report_bytes

Imports and analyses a file in a single session, returning both imported file bytes and analysis report bytes.

Args: input_file (Union[str, bytes, bytearray, io.BytesIO]): The input file path or bytes. output_file (Optional[str]): The output file path where the imported file will be written. output_analysis_report (Optional[str]): The output file path where the XML analysis report will be written. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply to the session. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: Tuple[Optional[bytes], Optional[bytes]]: A tuple of (import_file_bytes, analysis_report_bytes).

def import_and_analyse_directory( self, input_directory: str, output_directory: str | None = None, analysis_directory: str | None = None, content_management_policy: None | str | bytes | bytearray | _io.BytesIO | glasswall.content_management.policies.policy.Policy = None, raise_unsupported: bool = True):
1850    def import_and_analyse_directory(
1851        self,
1852        input_directory: str,
1853        output_directory: Optional[str] = None,
1854        analysis_directory: Optional[str] = None,
1855        content_management_policy: Union[None, str, bytes, bytearray, io.BytesIO, "glasswall.content_management.policies.policy.Policy"] = None,
1856        raise_unsupported: bool = True,
1857    ):
1858        """ Recursively processes all files in a directory using import and analyse mode with the given content management policy.
1859        Outputs are written to output_directory and analysis_directory maintaining the same structure as input_directory.
1860
1861        Args:
1862            input_directory (str): The input directory containing export .zip files to process.
1863            output_directory (Optional[str]): The output directory for imported files.
1864            analysis_directory (Optional[str]): The output directory for XML analysis reports.
1865            content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply.
1866            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
1867
1868        Returns:
1869            result_dict (dict): A dictionary mapping relative file paths to tuples of (import_file_bytes, analysis_report_bytes).
1870        """
1871        result_dict = {}
1872
1873        for input_file in utils.list_file_paths(input_directory):
1874            relative_path = os.path.relpath(input_file, input_directory)
1875            # Remove .zip extension from relative_path
1876            if relative_path.endswith(".zip"):
1877                relative_path = os.path.splitext(relative_path)[0]
1878            output_file = None if output_directory is None else os.path.join(os.path.abspath(output_directory), relative_path)
1879            output_analysis_report = None if analysis_directory is None else os.path.join(os.path.abspath(analysis_directory), relative_path + ".xml")
1880
1881            import_file_bytes, analysis_report_bytes = self.import_and_analyse_file(
1882                input_file=input_file,
1883                output_file=output_file,
1884                output_analysis_report=output_analysis_report,
1885                content_management_policy=content_management_policy,
1886                raise_unsupported=raise_unsupported,
1887            )
1888
1889            result_dict[relative_path] = (import_file_bytes, analysis_report_bytes)
1890
1891        return result_dict

Recursively processes all files in a directory using import and analyse mode with the given content management policy. Outputs are written to output_directory and analysis_directory maintaining the same structure as input_directory.

Args: input_directory (str): The input directory containing export .zip files to process. output_directory (Optional[str]): The output directory for imported files. analysis_directory (Optional[str]): The output directory for XML analysis reports. content_management_policy (Union[None, str, bytes, bytearray, io.BytesIO, glasswall.content_management.policies.policy.Policy], optional): The content management policy to apply. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: result_dict (dict): A dictionary mapping relative file paths to tuples of (import_file_bytes, analysis_report_bytes).

@functools.lru_cache()
def file_error_message(self, session: int) -> str:
1932    @functools.lru_cache()
1933    def file_error_message(self, session: int) -> str:
1934        """ Retrieve the Glasswall Session Process error message.
1935
1936        Args:
1937            session (int): The session integer.
1938
1939        Returns:
1940            error_message (str): The Glasswall Session Process error message.
1941        """
1942        # Validate arg types
1943        if not isinstance(session, int):
1944            raise TypeError(session)
1945
1946        result = self._GW2FileErrorMsg(session)
1947
1948        if result.status not in successes.success_codes:
1949            log.error(format_object(result))
1950            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
1951        else:
1952            log.debug(format_object(result))
1953
1954        return result.error_message

Retrieve the Glasswall Session Process error message.

Args: session (int): The session integer.

Returns: error_message (str): The Glasswall Session Process error message.

def GW2GetFileType(self, session: int, file_type_id):
1956    def GW2GetFileType(self, session: int, file_type_id):
1957        """ Retrieve the file type as a string.
1958
1959        Args:
1960            session (int): The session integer.
1961            file_type_id (int): The file type id.
1962
1963        Returns:
1964            file_type (str): The formal file name for the corresponding file id.
1965        """
1966        # Validate arg types
1967        if not isinstance(session, int):
1968            raise TypeError(session)
1969
1970        # API function declaration
1971        self.library.GW2GetFileType.argtypes = [
1972            ct.c_size_t,
1973            ct.c_size_t,
1974            ct.POINTER(ct.c_size_t),
1975            ct.POINTER(ct.c_void_p)
1976        ]
1977
1978        # Variable initialisation
1979        ct_session = ct.c_size_t(session)
1980        ct_file_type = ct.c_size_t(file_type_id)
1981        ct_buffer_length = ct.c_size_t()
1982        ct_buffer = ct.c_void_p()
1983
1984        # API call
1985        status = self.library.GW2GetFileType(
1986            ct_session,
1987            ct_file_type,
1988            ct.byref(ct_buffer_length),
1989            ct.byref(ct_buffer)
1990        )
1991
1992        if status not in successes.success_codes:
1993            log.error(f"\n\tsession: {session}\n\tstatus: {status}")
1994            raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
1995        else:
1996            log.debug(f"\n\tsession: {session}\n\tstatus: {status}")
1997
1998        # Editor wrote to a buffer, convert it to bytes
1999        file_type_bytes = utils.buffer_to_bytes(
2000            ct_buffer,
2001            ct_buffer_length
2002        )
2003
2004        file_type = file_type_bytes.decode()
2005
2006        return file_type

Retrieve the file type as a string.

Args: session (int): The session integer. file_type_id (int): The file type id.

Returns: file_type (str): The formal file name for the corresponding file id.

def GW2GetFileTypeID(self, session: int, file_type_str):
2008    def GW2GetFileTypeID(self, session: int, file_type_str):
2009        """ Retrieve the Glasswall file type id given a file type string.
2010
2011        Args:
2012            session (int): The session integer.
2013            file_type_str (str): The file type as a string.
2014
2015        Returns:
2016            file_type_id (str): The Glasswall file type id for the specified file type.
2017        """
2018        # Validate arg types
2019        if not isinstance(session, int):
2020            raise TypeError(session)
2021
2022        # API function declaration
2023        self.library.GW2GetFileTypeID.argtypes = [
2024            ct.c_size_t,
2025            ct.c_char_p,
2026            ct.POINTER(ct.c_size_t),
2027            ct.POINTER(ct.c_void_p)
2028        ]
2029
2030        # Variable initialisation
2031        ct_session = ct.c_size_t(session)
2032        ct_file_type = ct.c_char_p(file_type_str.encode('utf-8'))
2033        ct_buffer_length = ct.c_size_t()
2034        ct_buffer = ct.c_void_p()
2035
2036        # API call
2037        status = self.library.GW2GetFileTypeID(
2038            ct_session,
2039            ct_file_type,
2040            ct.byref(ct_buffer_length),
2041            ct.byref(ct_buffer)
2042        )
2043
2044        if status not in successes.success_codes:
2045            log.error(f"\n\tsession: {session}\n\tstatus: {status}")
2046            raise errors.error_codes.get(status, errors.UnknownErrorCode)(status)
2047        else:
2048            log.debug(f"\n\tsession: {session}\n\tstatus: {status}")
2049
2050        # Editor wrote to a buffer, convert it to bytes
2051        file_type_bytes = utils.buffer_to_bytes(
2052            ct_buffer,
2053            ct_buffer_length
2054        )
2055
2056        file_type_id = file_type_bytes.decode()
2057
2058        return file_type_id

Retrieve the Glasswall file type id given a file type string.

Args: session (int): The session integer. file_type_str (str): The file type as a string.

Returns: file_type_id (str): The Glasswall file type id for the specified file type.

def get_file_type_info(self, file_type: str | int):
2060    def get_file_type_info(self, file_type: Union[str, int]):
2061        """ Retrieve information about a file type based on its identifier.
2062
2063        Args:
2064            file_type (Union[str, int]): The file type identifier. This can be either a string representing a file
2065            extension (e.g. 'bmp') or an integer corresponding to a file type (e.g. 29).
2066
2067        Returns:
2068            - file_type_info (Union[int, str]): Depending on the input 'file_type':
2069                - If `file_type` is a string (e.g. 'bmp'):
2070                    - If the file type is recognised, returns an integer corresponding to that file type.
2071                    - If the file type is not recognised, returns 0.
2072                - If `file_type` is an integer (e.g. 29):
2073                    - If the integer corresponds to a recognised file type, returns a more detailed string description
2074                        of the file type (e.g. 'BMP Image').
2075                    - If the integer does not match any recognised file type, returns an empty string.
2076        """
2077        # Validate arg types
2078        if not isinstance(file_type, (str, int)):
2079            raise TypeError(file_type)
2080
2081        with utils.CwdHandler(self.library_path):
2082            with self.new_session() as session:
2083
2084                if isinstance(file_type, int):
2085                    file_type_info = self.GW2GetFileType(session, file_type)
2086                if isinstance(file_type, str):
2087                    file_type_info = self.GW2GetFileTypeID(session, file_type)
2088
2089                return file_type_info

Retrieve information about a file type based on its identifier.

Args: file_type (Union[str, int]): The file type identifier. This can be either a string representing a file extension (e.g. 'bmp') or an integer corresponding to a file type (e.g. 29).

Returns: - file_type_info (Union[int, str]): Depending on the input 'file_type': - If file_type is a string (e.g. 'bmp'): - If the file type is recognised, returns an integer corresponding to that file type. - If the file type is not recognised, returns 0. - If file_type is an integer (e.g. 29): - If the integer corresponds to a recognised file type, returns a more detailed string description of the file type (e.g. 'BMP Image'). - If the integer does not match any recognised file type, returns an empty string.

@utils.deprecated_function(replacement_function=get_file_type_info)
def get_file_info(self, *args, **kwargs):
2091    @utils.deprecated_function(replacement_function=get_file_type_info)
2092    def get_file_info(self, *args, **kwargs):
2093        """ Deprecated in 1.0.6. Use get_file_type_info. """
2094        pass

Deprecated in 1.0.6. Use get_file_type_info.

def register_report_file(self, session: int, output_file: str):
2125    def register_report_file(self, session: int, output_file: str):
2126        """ Register the report file path for the given session.
2127
2128        Args:
2129            session (int): The session integer.
2130            output_file (str): The file path of the report file.
2131
2132        Returns:
2133            gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.
2134        """
2135        # Validate arg types
2136        if not isinstance(session, int):
2137            raise TypeError(session)
2138        if not isinstance(output_file, (type(None), str)):
2139            raise TypeError(output_file)
2140
2141        result = self._GW2RegisterReportFile(session, output_file)
2142
2143        if result.status not in successes.success_codes:
2144            log.error(format_object(result))
2145            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2146        else:
2147            log.debug(format_object(result))
2148
2149        return result

Register the report file path for the given session.

Args: session (int): The session integer. output_file (str): The file path of the report file.

Returns: gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'output_file', 'status'.

def get_id_info(self, issue_id: int, raise_unsupported: bool = True):
2195    def get_id_info(self, issue_id: int, raise_unsupported: bool = True):
2196        """ Retrieves the group description for the given Issue ID. e.g. issue_id 96 returns "Document Processing Instances"
2197
2198        Args:
2199            issue_id (int): The issue id.
2200            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
2201
2202        Returns:
2203            id_info (str): The group description for the given Issue ID.
2204        """
2205        # Validate arg types
2206        if not isinstance(issue_id, int):
2207            raise TypeError(issue_id)
2208
2209        with utils.CwdHandler(self.library_path):
2210            with self.new_session() as session:
2211                result = self._GW2GetIdInfo(session, issue_id)
2212
2213                if result.status not in successes.success_codes:
2214                    log.error(format_object(result))
2215                    if raise_unsupported:
2216                        raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2217                else:
2218                    log.debug(format_object(result))
2219
2220                return result.id_info

Retrieves the group description for the given Issue ID. e.g. issue_id 96 returns "Document Processing Instances"

Args: issue_id (int): The issue id. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: id_info (str): The group description for the given Issue ID.

def get_all_id_info( self, output_file: str | None = None, raise_unsupported: bool = True) -> str:
2260    def get_all_id_info(self, output_file: Optional[str] = None, raise_unsupported: bool = True) -> str:
2261        """ Retrieves the XML containing all the Issue ID ranges with their group descriptions
2262
2263        Args:
2264            output_file (Optional[str]): The output file path where the analysis file will be written.
2265            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
2266
2267        Returns:
2268            all_id_info (str): A string XML analysis report containing all id info.
2269        """
2270        # Validate arg types
2271        if not isinstance(output_file, (type(None), str)):
2272            raise TypeError(output_file)
2273        if isinstance(output_file, str):
2274            output_file = os.path.abspath(output_file)
2275
2276        with utils.CwdHandler(self.library_path):
2277            with self.new_session() as session:
2278                result = self._GW2GetAllIdInfo(session)
2279
2280                if result.status not in successes.success_codes:
2281                    log.error(format_object(result))
2282                    if raise_unsupported:
2283                        raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2284                else:
2285                    log.debug(format_object(result))
2286
2287                if isinstance(output_file, str):
2288                    # GW2GetAllIdInfo is memory only, write to file
2289                    # make directories that do not exist
2290                    os.makedirs(os.path.dirname(output_file), exist_ok=True)
2291                    with open(output_file, "w") as f:
2292                        f.write(result.all_id_info)
2293
2294                return result.all_id_info

Retrieves the XML containing all the Issue ID ranges with their group descriptions

Args: output_file (Optional[str]): The output file path where the analysis file will be written. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: all_id_info (str): A string XML analysis report containing all id info.

def file_session_status_message(self, session: int, raise_unsupported: bool = True) -> str:
2341    def file_session_status_message(self, session: int, raise_unsupported: bool = True) -> str:
2342        """ Retrieves the Glasswall session status message. Gives a high level indication of the processing that was carried out.
2343
2344        Args:
2345            session (int): The session integer.
2346            raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.
2347
2348        Returns:
2349            result.message (str):The file session status message.
2350        """
2351        # Validate arg types
2352        if not isinstance(session, int):
2353            raise TypeError(session)
2354
2355        result = self._GW2FileSessionStatus(session)
2356
2357        if result.status not in successes.success_codes:
2358            log.error(format_object(result))
2359            if raise_unsupported:
2360                raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2361        else:
2362            log.debug(format_object(result))
2363
2364        return result.message

Retrieves the Glasswall session status message. Gives a high level indication of the processing that was carried out.

Args: session (int): The session integer. raise_unsupported (bool, optional): Default True. Raise exceptions when Glasswall encounters an error. Fail silently if False.

Returns: result.message (str):The file session status message.

def licence_details(self):
2390    def licence_details(self):
2391        """ Returns a string containing details of the licence.
2392
2393        Returns:
2394            result (str): A string containing details of the licence.
2395        """
2396        with self.new_session() as session:
2397            result = self._GW2LicenceDetails(session)
2398
2399            log.debug(f"\n\tsession: {session}\n\tGW2LicenceDetails: {result}")
2400
2401        return result

Returns a string containing details of the licence.

Returns: result (str): A string containing details of the licence.

def register_licence( self, session: int, input_file: str | bytes | bytearray | _io.BytesIO):
2524    def register_licence(self, session: int, input_file: Union[str, bytes, bytearray, io.BytesIO]):
2525        """ Registers a "gwkey.lic" licence from file path or memory.
2526
2527        Args:
2528            session (int): The session integer.
2529            input_file (Union[str, bytes, bytearray, io.BytesIO]): The "gwkey.lic" licence. It can be provided as a file path (str), bytes, bytearray, or a BytesIO object.
2530
2531        Returns:
2532            - result (glasswall.GwReturnObj): Depending on the input 'input_file':
2533                - If input_file is a str file path:
2534                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'status'.
2535
2536                - If input_file is a file in memory:
2537                    - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.
2538        """
2539        if isinstance(input_file, str):
2540            if not os.path.isfile(input_file):
2541                raise FileNotFoundError(input_file)
2542
2543            input_file = os.path.abspath(input_file)
2544
2545            result = self._GW2RegisterLicenceFile(session, input_file)
2546
2547        elif isinstance(input_file, (bytes, bytearray, io.BytesIO,)):
2548            # Convert bytearray and io.BytesIO to bytes
2549            input_file = utils.as_bytes(input_file)
2550
2551            result = self._GW2RegisterLicenceMemory(session, input_file)
2552
2553        else:
2554            raise TypeError(input_file)
2555
2556        if result.status not in successes.success_codes:
2557            log.error(format_object(result))
2558            raise errors.error_codes.get(result.status, errors.UnknownErrorCode)(result.status)
2559        else:
2560            log.debug(format_object(result))
2561
2562        return result

Registers a "gwkey.lic" licence from file path or memory.

Args: session (int): The session integer. input_file (Union[str, bytes, bytearray, io.BytesIO]): The "gwkey.lic" licence. It can be provided as a file path (str), bytes, bytearray, or a BytesIO object.

Returns: - result (glasswall.GwReturnObj): Depending on the input 'input_file': - If input_file is a str file path: - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'input_file', 'status'.

    - If input_file is a file in memory:
        - gw_return_object (glasswall.GwReturnObj): A GwReturnObj instance with the attributes 'session', 'buffer', 'buffer_length', 'status'.