1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
|
#!/usr/bin/env python3
"""
Definition of the beans used to represent the parsed objects
:authors: Thomas Calmant
:license: Apache License 2.0
:version: 0.4.4
:status: Alpha
..
Copyright 2024 Thomas Calmant
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from __future__ import absolute_import
import logging
from enum import IntEnum
from typing import Any, Dict, List, Optional, Set
from ..constants import ClassDescFlags, TypeCode
from ..modifiedutf8 import byte_to_int, decode_modified_utf8
from ..utils import UNICODE_TYPE
# ------------------------------------------------------------------------------
# Module version
__version_info__ = (0, 4, 4)
__version__ = ".".join(str(x) for x in __version_info__)
# Documentation strings format
__docformat__ = "restructuredtext en"
# ------------------------------------------------------------------------------
class ContentType(IntEnum):
"""
Types of objects
"""
INSTANCE = 0
CLASS = 1
ARRAY = 2
STRING = 3
ENUM = 4
CLASSDESC = 5
BLOCKDATA = 6
EXCEPTIONSTATE = 7
class ClassDataType(IntEnum):
"""
Class data types
"""
NOWRCLASS = 0
WRCLASS = 1
EXTERNAL_CONTENTS = 2
OBJECT_ANNOTATION = 3
class ClassDescType(IntEnum):
"""
Types of class descriptions
"""
NORMALCLASS = 0
PROXYCLASS = 1
class FieldType(IntEnum):
"""
Types of class fields
"""
BYTE = TypeCode.TYPE_BYTE.value
CHAR = TypeCode.TYPE_CHAR.value
DOUBLE = TypeCode.TYPE_DOUBLE.value
FLOAT = TypeCode.TYPE_FLOAT.value
INTEGER = TypeCode.TYPE_INTEGER.value
LONG = TypeCode.TYPE_LONG.value
SHORT = TypeCode.TYPE_SHORT.value
BOOLEAN = TypeCode.TYPE_BOOLEAN.value
ARRAY = TypeCode.TYPE_ARRAY.value
OBJECT = TypeCode.TYPE_OBJECT.value
def type_code(self):
# type: () -> TypeCode
"""
Converts this FieldType to its matching TypeCode
"""
return TypeCode(self.value)
class ParsedJavaContent(object): # pylint:disable=R205
"""
Generic representation of data parsed from the stream
"""
def __init__(self, content_type):
# type: (ContentType) -> None
self.type = content_type # type: ContentType
self.is_exception = False # type: bool
self.handle = 0 # type: int
def __str__(self):
return "[ParseJavaObject 0x{0:x} - {1}]".format(self.handle, self.type)
__repr__ = __str__
def dump(self, indent=0):
# type: (int) -> str
"""
Base implementation of a parsed object
"""
return "\t" * indent + str(self)
def validate(self):
"""
Validity check on the object
"""
pass
class ExceptionState(ParsedJavaContent):
"""
Representation of a failed parsing
"""
def __init__(self, exception_object, data):
# type: (ParsedJavaContent, bytes) -> None
super(ExceptionState, self).__init__(ContentType.EXCEPTIONSTATE)
self.exception_object = exception_object
self.stream_data = data
self.handle = exception_object.handle
def dump(self, indent=0):
# type: (int) -> str
"""
Returns a dump representation of the exception
"""
return "\t" * indent + "[ExceptionState {0:x}]".format(self.handle)
class ExceptionRead(Exception):
"""
Exception used to indicate that an exception object has been parsed
"""
def __init__(self, content):
# type: (ParsedJavaContent) -> None
self.exception_object = content
class JavaString(ParsedJavaContent):
"""
Represents a Java string
"""
def __init__(self, handle, data):
# type: (int, bytes) -> None
super(JavaString, self).__init__(ContentType.STRING)
self.handle = handle
value, length = decode_modified_utf8(data)
self.value = value # type: str
self.length = length # type: int
def __repr__(self):
return repr(self.value)
def __str__(self):
return self.value
def dump(self, indent=0):
# type: (int) -> str
"""
Returns a dump representation of the string
"""
return "\t" * indent + "[String {0:x}: {1}]".format(
self.handle, repr(self.value)
)
def __hash__(self):
return hash(self.value)
def __eq__(self, other):
return self.value == other
class JavaField:
"""
Represents a field in a Java class description
"""
def __init__(self, field_type, name, class_name=None):
# type: (FieldType, str, Optional[JavaString]) -> None
self.type = field_type
self.name = name
self.class_name = class_name
self.is_inner_class_reference = False
if self.class_name:
self.validate(self.class_name.value)
def validate(self, java_type):
# type: (str) -> None
"""
Validates the type given as parameter
"""
if self.type == FieldType.OBJECT:
if not java_type:
raise ValueError("Class name can't be empty")
if java_type[0] != "L" or java_type[-1] != ";":
raise ValueError(
"Invalid object field type: {0}".format(java_type)
)
class JavaClassDesc(ParsedJavaContent):
"""
Represents the description of a class
"""
def __init__(self, class_desc_type):
# type: (ClassDescType) -> None
super(JavaClassDesc, self).__init__(ContentType.CLASSDESC)
# Type of class description
self.class_type = class_desc_type # type: ClassDescType
# Class name
self.name = None # type: Optional[str]
# Serial version UID
self.serial_version_uid = 0 # type: int
# Description flags byte
self.desc_flags = 0 # type: int
# Fields in the class
self.fields = [] # type: List[JavaField]
# Inner classes
self.inner_classes = [] # type: List[JavaClassDesc]
# List of annotations objects
self.annotations = [] # type: List[ParsedJavaContent]
# 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]
# Set of enum constants
self.enum_constants = set() # type: Set[str]
# Flag to indicate if this is an inner class
self.is_inner_class = False # type: bool
# Flag to indicate if this is a local inner class
self.is_local_inner_class = False # type: bool
# Flag to indicate if this is a static member class
self.is_static_member_class = False # type: bool
def __str__(self):
return "[classdesc 0x{0:x}: name {1}, uid {2}]".format(
self.handle, self.name, self.serial_version_uid
)
__repr__ = __str__
def dump(self, indent=0):
# type: (int) -> str
"""
Returns a dump representation of the exception
"""
return "\t" * indent + "[classdesc 0x{0:x}: name {1}, uid {2}]".format(
self.handle, self.name, self.serial_version_uid
)
@property
def serialVersionUID(self): # pylint:disable=C0103
"""
Mimics the javaobj API
"""
return self.serial_version_uid
@property
def flags(self):
"""
Mimics the javaobj API
"""
return self.desc_flags
@property
def fields_names(self):
"""
Mimics the javaobj API
"""
return [field.name for field in self.fields]
@property
def fields_types(self):
"""
Mimics the javaobj API
"""
return [field.type for field in self.fields]
@property
def data_type(self):
"""
Computes the data type of this class (Write, No Write, Annotation)
"""
if ClassDescFlags.SC_SERIALIZABLE & self.desc_flags:
return (
ClassDataType.WRCLASS
if (ClassDescFlags.SC_WRITE_METHOD & self.desc_flags)
else ClassDataType.NOWRCLASS
)
if 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
"""
Determines if this is an array type
"""
return self.name.startswith("[") if self.name else False
def get_hierarchy(self, classes):
# type: (List["JavaClassDesc"]) -> None
"""
Generates a list of class descriptions in this class's hierarchy, in
the order described by the Object Stream Serialization Protocol.
This is the order in which fields are read from the stream.
:param classes: A list to be filled in with the hierarchy
"""
if self.super_class is not None:
if self.super_class.class_type == ClassDescType.PROXYCLASS:
logging.warning("Hit a proxy class in super class hierarchy")
else:
self.super_class.get_hierarchy(classes)
classes.append(self)
def validate(self):
"""
Checks the validity of this class description
"""
serial_or_extern = (
ClassDescFlags.SC_SERIALIZABLE | ClassDescFlags.SC_EXTERNALIZABLE
)
if (self.desc_flags & serial_or_extern) == 0 and self.fields:
raise ValueError(
"Non-serializable, non-externalizable class has fields"
)
if self.desc_flags & serial_or_extern == serial_or_extern:
raise ValueError("Class is both serializable and externalizable")
if self.desc_flags & ClassDescFlags.SC_ENUM:
if self.fields or self.interfaces:
raise ValueError(
"Enums shouldn't implement interfaces "
"or have non-constant fields"
)
else:
if self.enum_constants:
raise ValueError(
"Non-enum classes shouldn't have enum constants"
)
class JavaInstance(ParsedJavaContent):
"""
Represents an instance of Java object
"""
def __init__(self):
super(JavaInstance, self).__init__(ContentType.INSTANCE)
self.classdesc = None # type: JavaClassDesc
self.field_data = {} # type: Dict[JavaClassDesc, Dict[JavaField, Any]]
self.annotations = (
{}
) # type: Dict[JavaClassDesc, List[ParsedJavaContent]]
self.is_external_instance = False
def __str__(self):
return "[instance 0x{0:x}: type {1}]".format(
self.handle, self.classdesc.name
)
__repr__ = __str__
def dump(self, indent=0):
# type: (int) -> str
"""
Returns a dump representation of the exception
"""
prefix = "\t" * indent
sub_prefix = "\t" * (indent + 1)
dump = [
prefix
+ "[instance 0x{0:x}: {1:x} / {2}]".format(
self.handle, self.classdesc.handle, self.classdesc.name
)
]
for cd, annotations in self.annotations.items():
dump.append(
"{0}{1} -- {2} annotations".format(
prefix, cd.name, len(annotations)
)
)
for ann in annotations:
dump.append(sub_prefix + repr(ann))
for cd, fields in self.field_data.items():
dump.append(
"{0}{1} -- {2} fields".format(prefix, cd.name, len(fields))
)
for field, value in fields.items():
if isinstance(value, ParsedJavaContent):
if self.handle != 0 and value.handle == self.handle:
value_str = "this"
else:
value_str = "\n" + value.dump(indent + 2)
else:
value_str = repr(value)
dump.append(
"{0}{1} {2}: {3}".format(
sub_prefix, field.type.name, field.name, value_str
)
)
dump.append(prefix + "[/instance 0x{0:x}]".format(self.handle))
return "\n".join(dump)
def __getattr__(self, name):
"""
Returns the field with the given name
"""
for cd_fields in self.field_data.values():
for field, value in cd_fields.items():
if field.name == name:
return value
raise AttributeError(name)
def get_class(self):
"""
Returns the class of this instance
"""
return self.classdesc
def load_from_blockdata(
self, parser, reader, indent=0
): # pylint:disable=W0613,R0201
"""
Reads content stored in a block data.
This method is called only if the class description has both the
``SC_EXTERNALIZABLE`` and ``SC_BLOCK_DATA`` flags set.
The stream parsing will stop and fail if this method returns False.
:param parser: The JavaStreamParser in use
:param reader: The underlying data stream reader
:param indent: Indentation to use in logs
:return: True on success, False on error
"""
return False
def load_from_instance(self, indent=0): # pylint:disable=W0613,R0201
# type: (int) -> bool
"""
Updates the content of this instance from its parsed fields and
annotations
:param indent: Indentation to use in logs
:return: True on success, False on error (currently ignored)
"""
return False
class JavaClass(ParsedJavaContent):
"""
Represents a stored Java class
"""
def __init__(self, handle, class_desc):
# type: (int, JavaClassDesc) -> None
super(JavaClass, self).__init__(ContentType.CLASS)
self.handle = handle
self.classdesc = class_desc
def __str__(self):
return "[class 0x{0:x}: {1}]".format(self.handle, self.classdesc)
__repr__ = __str__
@property
def name(self):
"""
Mimics the javaobj API
"""
return self.classdesc.name
class JavaEnum(ParsedJavaContent):
"""
Represents an enumeration value
"""
def __init__(self, handle, class_desc, value):
# type: (int, JavaClassDesc, JavaString) -> None
super(JavaEnum, self).__init__(ContentType.ENUM)
self.handle = handle
self.classdesc = class_desc
self.value = value
def __str__(self):
return "[Enum 0x{0:x}: {1}]".format(self.handle, self.value)
__repr__ = __str__
@property
def constant(self):
"""
Mimics the javaobj API
"""
return self.value
class JavaArray(ParsedJavaContent, list):
"""
Represents a Java array
"""
def __init__(self, handle, class_desc, field_type, content):
# type: (int, JavaClassDesc, FieldType, List[Any]) -> None
list.__init__(self, content)
ParsedJavaContent.__init__(self, ContentType.ARRAY)
self.handle = handle
self.classdesc = class_desc
self.field_type = field_type
self.data = content
def __str__(self):
return "[{0}]".format(", ".join(repr(x) for x in self))
__repr__ = __str__
def dump(self, indent=0):
# type: (int) -> str
"""
Returns a dump representation of the array
"""
prefix = "\t" * indent
sub_prefix = "\t" * (indent + 1)
dump = [
"{0}[array 0x{1:x}: {2} items - stored as {3}]".format(
prefix, self.handle, len(self), type(self.data).__name__
)
]
for x in self:
if isinstance(x, ParsedJavaContent):
if self.handle != 0 and x.handle == self.handle:
dump.append("this,")
else:
dump.append(x.dump(indent + 1) + ",")
else:
dump.append(sub_prefix + repr(x) + ",")
dump.append(prefix + "[/array 0x{0:x}]".format(self.handle))
return "\n".join(dump)
@property
def _data(self):
"""
Mimics the javaobj API
"""
return tuple(self)
class BlockData(ParsedJavaContent):
"""
Represents a data block
"""
def __init__(self, data):
# type: (bytes) -> None
super(BlockData, self).__init__(ContentType.BLOCKDATA)
self.data = data
def __str__(self):
return "[blockdata 0x{0:x}: {1} bytes]".format(
self.handle, len(self.data)
)
def __repr__(self):
return repr(self.data)
def __eq__(self, other):
if isinstance(other, (str, UNICODE_TYPE)):
other_data = tuple(ord(x) for x in other)
elif isinstance(other, bytes):
other_data = tuple(byte_to_int(x) for x in other)
else:
# Can't compare
return False
return other_data == tuple(byte_to_int(x) for x in self.data)
|