Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions javaobj/v2/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
25 changes: 24 additions & 1 deletion javaobj/v2/beans.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -245,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]

Expand Down Expand Up @@ -304,6 +316,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
"""
Expand Down Expand Up @@ -368,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(
Expand Down
118 changes: 57 additions & 61 deletions javaobj/v2/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,18 @@
ExceptionRead,
ClassDescType,
FieldType,
ClassDataType,
)
from .stream import DataStreamReader
from .transformers import DefaultObjectTransformer
from ..constants import (
ClassDescFlags,
StreamConstants,
TerminalCode,
TypeCode,
PRIMITIVE_TYPES,
)

from ..modifiedutf8 import decode_modified_utf8

# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -276,7 +279,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
Expand All @@ -290,6 +293,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:
Expand Down Expand Up @@ -321,7 +327,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)
Expand All @@ -338,12 +344,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
Expand All @@ -352,57 +356,47 @@ 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
class_desc.serial_version_uid = serial_version_uid
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
self._set_handle(handle, class_desc)
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")
Expand All @@ -424,10 +418,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
Expand All @@ -442,8 +446,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)

Expand Down Expand Up @@ -489,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
"""
Expand All @@ -503,40 +511,28 @@ 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:
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

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
if not instance.load_from_blockdata(self, self.__reader):
# Can't read :/
raise ValueError(
"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
Expand Down Expand Up @@ -568,11 +564,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:
Expand Down
4 changes: 1 addition & 3 deletions javaobj/v2/transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down