From d44f84b02a06300be41c29da1e539cd0ebe4e6a8 Mon Sep 17 00:00:00 2001 From: Fede A Date: Thu, 9 Apr 2020 01:00:45 -0300 Subject: [PATCH 1/3] adds suport for SC_WRITE_METHOD --- javaobj/v2/api.py | 16 +++++++ javaobj/v2/beans.py | 21 ++++++++- javaobj/v2/core.py | 111 +++++++++++++++++++------------------------- 3 files changed, 85 insertions(+), 63 deletions(-) diff --git a/javaobj/v2/api.py b/javaobj/v2/api.py index b02bd74..c349711 100644 --- a/javaobj/v2/api.py +++ b/javaobj/v2/api.py @@ -78,3 +78,19 @@ def load_array(self, reader, field_type, size): :param size: Number of elements in the array """ return None + + def load_custom_writeObject(self, parser, reader, name): + """ + Reads content stored from a custom writeObject. + + This method is called only if the class description has both the + ``SC_SERIALIZABLE`` and ``SC_WRITE_METHOD`` flags set. + + The stream parsing will stop and fail if this method returns None. + + :param parser: The JavaStreamParser in use + :param reader: The data stream reader + :param name: The class description name + :return: An array with the parsed fields or None + """ + return None diff --git a/javaobj/v2/beans.py b/javaobj/v2/beans.py index 4db7d89..f33276a 100644 --- a/javaobj/v2/beans.py +++ b/javaobj/v2/beans.py @@ -60,6 +60,16 @@ class ContentType(IntEnum): BLOCKDATA = 6 EXCEPTIONSTATE = 7 +class ClassDataType(IntEnum): + """ + Class data types + """ + + NOWRCLASS = 0 + WRCLASS = 1 + EXTERNAL_CONTENTS = 2 + OBJECT_ANNOTATION = 3 + class ClassDescType(IntEnum): """ @@ -181,7 +191,6 @@ def __hash__(self): def __eq__(self, other): return self.value == other - class JavaField: """ Represents a field in a Java class description @@ -304,6 +313,16 @@ def fields_types(self): """ return [field.type for field in self.fields] + @property + def data_type(self): + if (ClassDescFlags.SC_SERIALIZABLE & self.desc_flags): + return ClassDataType.WRCLASS if (ClassDescFlags.SC_WRITE_METHOD & self.desc_flags) else ClassDataType.NOWRCLASS + elif (ClassDescFlags.SC_EXTERNALIZABLE & self.desc_flags): + return ClassDataType.OBJECT_ANNOTATION if (ClassDescFlags.SC_WRITE_METHOD & self.desc_flags) else ClassDataType.EXTERNAL_CONTENTS + + raise ValueError("Unhandled Class Data Type") + + def is_array_class(self): # type: () -> bool """ diff --git a/javaobj/v2/core.py b/javaobj/v2/core.py index aa6cd79..d3e55d2 100644 --- a/javaobj/v2/core.py +++ b/javaobj/v2/core.py @@ -48,6 +48,7 @@ ExceptionRead, ClassDescType, FieldType, + ClassDataType, ) from .stream import DataStreamReader from ..constants import ( @@ -57,6 +58,7 @@ TypeCode, PRIMITIVE_TYPES, ) + from ..modifiedutf8 import decode_modified_utf8 # ------------------------------------------------------------------------------ @@ -276,7 +278,7 @@ def _do_null(self, _): """ return None - def _read_content(self, type_code, block_data): + def _read_content(self, type_code, block_data, class_desc=None): # type: (int, bool) -> ParsedJavaContent """ Parses the next content @@ -290,6 +292,9 @@ def _read_content(self, type_code, block_data): try: handler = self.__type_code_handlers[type_code] except KeyError: + '''Looking for an external reader''' + if class_desc and class_desc.data_type == ClassDataType.WRCLASS: + return self._custom_readObject(class_desc.name) raise ValueError("Unknown type code: 0x{0:x}".format(type_code)) else: try: @@ -297,7 +302,7 @@ def _read_content(self, type_code, block_data): except ExceptionRead as ex: return ex.exception_object - def _read_new_string(self, type_code): + def _read_new_string(self, type_code, field_name=None): # type: (int) -> JavaString """ Reads a Java String @@ -321,7 +326,7 @@ def _read_new_string(self, type_code): raise ValueError("Invalid string length: {0}".format(length)) elif length < 65536: self._log.warning("Small string stored as a long one") - + # Parse the content data = self.__fd.read(length) java_str = JavaString(handle, data) @@ -338,12 +343,10 @@ def _read_classdesc(self): type_code = self.__reader.read_byte() return self._do_classdesc(type_code) - def _do_classdesc(self, type_code, must_be_new=False): + def _do_classdesc(self, type_code): # type: (int, bool) -> JavaClassDesc """ Parses a class description - - :param must_be_new: Check if the class description is really a new one """ if type_code == TerminalCode.TC_CLASSDESC: # Do the real job @@ -352,32 +355,29 @@ def _do_classdesc(self, type_code, must_be_new=False): handle = self._new_handle() desc_flags = self.__reader.read_byte() nb_fields = self.__reader.read_short() + if nb_fields < 0: raise ValueError("Invalid field count: {0}".format(nb_fields)) fields = [] # type: List[JavaField] for _ in range(nb_fields): field_type = self.__reader.read_byte() - if field_type in PRIMITIVE_TYPES: - # Primitive type - field_name = self.__reader.read_UTF() - fields.append(JavaField(FieldType(field_type), field_name)) - elif field_type in (TypeCode.TYPE_OBJECT, TypeCode.TYPE_ARRAY,): - # Array or object type - field_name = self.__reader.read_UTF() + field_name = self.__reader.read_UTF() + class_name = None + + if field_type in (TypeCode.TYPE_OBJECT, TypeCode.TYPE_ARRAY): # String type code str_type_code = self.__reader.read_byte() class_name = self._read_new_string(str_type_code) - fields.append( - JavaField( - FieldType(field_type), field_name, class_name, - ), - ) - else: + elif field_type not in PRIMITIVE_TYPES: raise ValueError( "Invalid field type char: 0x{0:x}".format(field_type) ) + fields.append(JavaField( + FieldType(field_type), field_name, class_name + )) + # Setup the class description bean class_desc = JavaClassDesc(ClassDescType.NORMALCLASS) class_desc.name = name @@ -385,7 +385,7 @@ def _do_classdesc(self, type_code, must_be_new=False): class_desc.handle = handle class_desc.desc_flags = desc_flags class_desc.fields = fields - class_desc.annotations = self._read_class_annotations() + class_desc.annotations = self._read_class_annotations(class_desc) class_desc.super_class = self._read_classdesc() # Store the reference to the parsed bean @@ -393,16 +393,9 @@ def _do_classdesc(self, type_code, must_be_new=False): return class_desc elif type_code == TerminalCode.TC_NULL: # Null reference - if must_be_new: - raise ValueError("Got Null instead of a new class description") return None elif type_code == TerminalCode.TC_REFERENCE: # Reference to an already loading class description - if must_be_new: - raise ValueError( - "Got a reference instead of a new class description" - ) - previous = self._do_reference() if not isinstance(previous, JavaClassDesc): raise ValueError("Referenced object is not a class description") @@ -424,10 +417,20 @@ def _do_classdesc(self, type_code, must_be_new=False): # Store the reference to the parsed bean self._set_handle(handle, class_desc) return class_desc - + raise ValueError("Expected a valid class description starter") - def _read_class_annotations(self): + + def _custom_readObject(self, class_name): + self.__fd.seek(-1, os.SEEK_CUR) + for transformer in self.__transformers: + class_data = transformer.load_custom_writeObject(self, self.__reader, class_name) + if class_data: + return class_data + raise ValueError("Custom readObject can not be processed") + + + def _read_class_annotations(self, class_desc=None): # type: () -> List[ParsedJavaContent] """ Reads the annotations associated to a class @@ -442,8 +445,8 @@ def _read_class_annotations(self): # Reset references self._reset() continue + java_object = self._read_content(type_code, True, class_desc) - java_object = self._read_content(type_code, True) if java_object is not None and java_object.is_exception: raise ExceptionRead(java_object) @@ -503,31 +506,16 @@ def _read_class_data(self, instance): for cd in classes: values = {} # type: Dict[JavaField, Any] - if cd.desc_flags & ClassDescFlags.SC_SERIALIZABLE: - if cd.desc_flags & ClassDescFlags.SC_EXTERNALIZABLE: - raise ValueError( - "SC_EXTERNALIZABLE & SC_SERIALIZABLE encountered" - ) - - for field in cd.fields: - values[field] = self._read_field_value(field.type) - - all_data[cd] = values - - if cd.desc_flags & ClassDescFlags.SC_WRITE_METHOD: - if cd.desc_flags & ClassDescFlags.SC_ENUM: - raise ValueError( - "SC_ENUM & SC_WRITE_METHOD encountered!" - ) - - annotations[cd] = self._read_class_annotations() - elif cd.desc_flags & ClassDescFlags.SC_EXTERNALIZABLE: - if cd.desc_flags & ClassDescFlags.SC_SERIALIZABLE: - raise ValueError( - "SC_EXTERNALIZABLE & SC_SERIALIZABLE encountered" - ) - - if cd.desc_flags & ClassDescFlags.SC_BLOCK_DATA: + cd.validate() + if cd.data_type == ClassDataType.NOWRCLASS or cd.data_type == ClassDataType.WRCLASS: + if cd.data_type == ClassDataType.NOWRCLASS: + for field in cd.fields: + values[field] = self._read_field_value(field.type) + all_data[cd] = values + else: + annotations[cd] = self._read_class_annotations(cd) + else: + if cd.data_type == ClassDataType.OBJECT_ANNOTATION: # Call the transformer if possible if not instance.load_from_blockdata(self, self.__reader): # Can't read :/ @@ -535,8 +523,7 @@ def _read_class_data(self, instance): "hit externalizable with nonzero SC_BLOCK_DATA; " "can't interpret data" ) - - annotations[cd] = self._read_class_annotations() + annotations[cd] = self._read_class_annotations(cd) # Fill the instance object instance.annotations = annotations @@ -568,11 +555,11 @@ def _read_field_value(self, field_type): return self.__reader.read_bool() elif field_type in (FieldType.OBJECT, FieldType.ARRAY): sub_type_code = self.__reader.read_byte() - if ( - field_type == FieldType.ARRAY - and sub_type_code != TerminalCode.TC_ARRAY - ): - raise ValueError("Array type listed, but type code != TC_ARRAY") + if field_type == FieldType.ARRAY: + if sub_type_code == TerminalCode.TC_REFERENCE: + return self._do_classdesc(sub_type_code) + elif sub_type_code != TerminalCode.TC_ARRAY: + raise ValueError("Array type listed, but type code != TC_ARRAY") content = self._read_content(sub_type_code, False) if content is not None and content.is_exception: From 3d0a1a45f4d524e45ba479d4f2cbfbd961f9bd6a Mon Sep 17 00:00:00 2001 From: Fede A Date: Thu, 9 Apr 2020 01:07:37 -0300 Subject: [PATCH 2/3] removes unused parameter --- javaobj/v2/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javaobj/v2/core.py b/javaobj/v2/core.py index d3e55d2..9af8b48 100644 --- a/javaobj/v2/core.py +++ b/javaobj/v2/core.py @@ -302,7 +302,7 @@ def _read_content(self, type_code, block_data, class_desc=None): except ExceptionRead as ex: return ex.exception_object - def _read_new_string(self, type_code, field_name=None): + def _read_new_string(self, type_code): # type: (int) -> JavaString """ Reads a Java String From 574b076d8c533f454b0f6ab38e386a8ee009180e Mon Sep 17 00:00:00 2001 From: Fede A Date: Sun, 12 Apr 2020 23:20:57 -0300 Subject: [PATCH 3/3] fixes _read_class_data --- javaobj/v2/beans.py | 4 ++++ javaobj/v2/core.py | 15 ++++++++++++--- javaobj/v2/transformers.py | 4 +--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/javaobj/v2/beans.py b/javaobj/v2/beans.py index f33276a..ee7ea07 100644 --- a/javaobj/v2/beans.py +++ b/javaobj/v2/beans.py @@ -254,6 +254,9 @@ def __init__(self, class_desc_type): # The super class of this one, if any self.super_class = None # type: Optional[JavaClassDesc] + # Indicates if it is a super class + self.is_super_class = False + # List of the interfaces of the class self.interfaces = [] # type: List[str] @@ -387,6 +390,7 @@ def __init__(self): self.annotations = ( {} ) # type: Dict[JavaClassDesc, List[ParsedJavaContent]] + self.is_external_instance = False def __str__(self): return "[instance 0x{0:x}: type {1}]".format( diff --git a/javaobj/v2/core.py b/javaobj/v2/core.py index 9af8b48..d398c0e 100644 --- a/javaobj/v2/core.py +++ b/javaobj/v2/core.py @@ -51,6 +51,7 @@ ClassDataType, ) from .stream import DataStreamReader +from .transformers import DefaultObjectTransformer from ..constants import ( ClassDescFlags, StreamConstants, @@ -492,6 +493,10 @@ def _do_object(self, type_code=0): self._log.debug("Done reading object handle %x", handle) return instance + def _is_default_supported(self, class_name): + default_transf = [x for x in self.__transformers if isinstance(x, DefaultObjectTransformer)] + return len(default_transf) and class_name in default_transf[0]._type_mapper + def _read_class_data(self, instance): # type: (JavaInstance) -> None """ @@ -508,12 +513,16 @@ def _read_class_data(self, instance): values = {} # type: Dict[JavaField, Any] cd.validate() if cd.data_type == ClassDataType.NOWRCLASS or cd.data_type == ClassDataType.WRCLASS: - if cd.data_type == ClassDataType.NOWRCLASS: + read_custom_data = cd.data_type == ClassDataType.WRCLASS and cd.is_super_class and not self._is_default_supported(cd.name) + if read_custom_data or cd.data_type == ClassDataType.WRCLASS and instance.is_external_instance: + annotations[cd] = self._read_class_annotations(cd) + else: for field in cd.fields: values[field] = self._read_field_value(field.type) all_data[cd] = values - else: - annotations[cd] = self._read_class_annotations(cd) + + if cd.data_type == ClassDataType.WRCLASS: + annotations[cd] = self._read_class_annotations(cd) else: if cd.data_type == ClassDataType.OBJECT_ANNOTATION: # Call the transformer if possible diff --git a/javaobj/v2/transformers.py b/javaobj/v2/transformers.py index fa99186..c9da287 100644 --- a/javaobj/v2/transformers.py +++ b/javaobj/v2/transformers.py @@ -37,9 +37,7 @@ # Javaobj from .api import ObjectTransformer -from .beans import JavaClassDesc, JavaInstance -from .core import JavaStreamParser -from .stream import DataStreamReader +from .beans import JavaInstance from ..constants import TerminalCode, TypeCode from ..utils import to_bytes, log_error, log_debug, read_struct, read_string