Aluode/PerceptionLabPortable
0
1 2__all__ = [3 'BaseConstructor',4 'SafeConstructor',5 'FullConstructor',6 'UnsafeConstructor',7 'Constructor',8 'ConstructorError'9]10 11from .error import *12from .nodes import *13 14import collections.abc, datetime, base64, binascii, re, sys, types15 16class ConstructorError(MarkedYAMLError):17 pass18 19class BaseConstructor:20 21 yaml_constructors = {}22 yaml_multi_constructors = {}23 24 def __init__(self):25 self.constructed_objects = {}26 self.recursive_objects = {}27 self.state_generators = []28 self.deep_construct = False29 30 def check_data(self):31 # If there are more documents available?32 return self.check_node()33 34 def check_state_key(self, key):35 """Block special attributes/methods from being set in a newly created36 object, to prevent user-controlled methods from being called during37 deserialization"""38 if self.get_state_keys_blacklist_regexp().match(key):39 raise ConstructorError(None, None,40 "blacklisted key '%s' in instance state found" % (key,), None)41 42 def get_data(self):43 # Construct and return the next document.44 if self.check_node():45 return self.construct_document(self.get_node())46 47 def get_single_data(self):48 # Ensure that the stream contains a single document and construct it.49 node = self.get_single_node()50 if node is not None:51 return self.construct_document(node)52 return None53 54 def construct_document(self, node):55 data = self.construct_object(node)56 while self.state_generators:57 state_generators = self.state_generators58 self.state_generators = []59 for generator in state_generators:60 for dummy in generator:61 pass62 self.constructed_objects = {}63 self.recursive_objects = {}64 self.deep_construct = False65 return data66 67 def construct_object(self, node, deep=False):68 if node in self.constructed_objects:69 return self.constructed_objects[node]70 if deep:71 old_deep = self.deep_construct72 self.deep_construct = True73 if node in self.recursive_objects:74 raise ConstructorError(None, None,75 "found unconstructable recursive node", node.start_mark)76 self.recursive_objects[node] = None77 constructor = None78 tag_suffix = None79 if node.tag in self.yaml_constructors:80 constructor = self.yaml_constructors[node.tag]81 else:82 for tag_prefix in self.yaml_multi_constructors:83 if tag_prefix is not None and node.tag.startswith(tag_prefix):84 tag_suffix = node.tag[len(tag_prefix):]85 constructor = self.yaml_multi_constructors[tag_prefix]86 break87 else:88 if None in self.yaml_multi_constructors:89 tag_suffix = node.tag90 constructor = self.yaml_multi_constructors[None]91 elif None in self.yaml_constructors:92 constructor = self.yaml_constructors[None]93 elif isinstance(node, ScalarNode):94 constructor = self.__class__.construct_scalar95 elif isinstance(node, SequenceNode):96 constructor = self.__class__.construct_sequence97 elif isinstance(node, MappingNode):98 constructor = self.__class__.construct_mapping99 if tag_suffix is None:100 data = constructor(self, node)101 else:102 data = constructor(self, tag_suffix, node)103 if isinstance(data, types.GeneratorType):104 generator = data105 data = next(generator)106 if self.deep_construct:107 for dummy in generator:108 pass109 else:110 self.state_generators.append(generator)111 self.constructed_objects[node] = data112 del self.recursive_objects[node]113 if deep:114 self.deep_construct = old_deep115 return data116 117 def construct_scalar(self, node):118 if not isinstance(node, ScalarNode):119 raise ConstructorError(None, None,120 "expected a scalar node, but found %s" % node.id,121 node.start_mark)122 return node.value123 124 def construct_sequence(self, node, deep=False):125 if not isinstance(node, SequenceNode):126 raise ConstructorError(None, None,127 "expected a sequence node, but found %s" % node.id,128 node.start_mark)129 return [self.construct_object(child, deep=deep)130 for child in node.value]131 132 def construct_mapping(self, node, deep=False):133 if not isinstance(node, MappingNode):134 raise ConstructorError(None, None,135 "expected a mapping node, but found %s" % node.id,136 node.start_mark)137 mapping = {}138 for key_node, value_node in node.value:139 key = self.construct_object(key_node, deep=deep)140 if not isinstance(key, collections.abc.Hashable):141 raise ConstructorError("while constructing a mapping", node.start_mark,142 "found unhashable key", key_node.start_mark)143 value = self.construct_object(value_node, deep=deep)144 mapping[key] = value145 return mapping146 147 def construct_pairs(self, node, deep=False):148 if not isinstance(node, MappingNode):149 raise ConstructorError(None, None,150 "expected a mapping node, but found %s" % node.id,151 node.start_mark)152 pairs = []153 for key_node, value_node in node.value:154 key = self.construct_object(key_node, deep=deep)155 value = self.construct_object(value_node, deep=deep)156 pairs.append((key, value))157 return pairs158 159 @classmethod160 def add_constructor(cls, tag, constructor):161 if not 'yaml_constructors' in cls.__dict__:162 cls.yaml_constructors = cls.yaml_constructors.copy()163 cls.yaml_constructors[tag] = constructor164 165 @classmethod166 def add_multi_constructor(cls, tag_prefix, multi_constructor):167 if not 'yaml_multi_constructors' in cls.__dict__:168 cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy()169 cls.yaml_multi_constructors[tag_prefix] = multi_constructor170 171class SafeConstructor(BaseConstructor):172 173 def construct_scalar(self, node):174 if isinstance(node, MappingNode):175 for key_node, value_node in node.value:176 if key_node.tag == 'tag:yaml.org,2002:value':177 return self.construct_scalar(value_node)178 return super().construct_scalar(node)179 180 def flatten_mapping(self, node):181 merge = []182 index = 0183 while index < len(node.value):184 key_node, value_node = node.value[index]185 if key_node.tag == 'tag:yaml.org,2002:merge':186 del node.value[index]187 if isinstance(value_node, MappingNode):188 self.flatten_mapping(value_node)189 merge.extend(value_node.value)190 elif isinstance(value_node, SequenceNode):191 submerge = []192 for subnode in value_node.value:193 if not isinstance(subnode, MappingNode):194 raise ConstructorError("while constructing a mapping",195 node.start_mark,196 "expected a mapping for merging, but found %s"197 % subnode.id, subnode.start_mark)198 self.flatten_mapping(subnode)199 submerge.append(subnode.value)200 submerge.reverse()201 for value in submerge:202 merge.extend(value)203 else:204 raise ConstructorError("while constructing a mapping", node.start_mark,205 "expected a mapping or list of mappings for merging, but found %s"206 % value_node.id, value_node.start_mark)207 elif key_node.tag == 'tag:yaml.org,2002:value':208 key_node.tag = 'tag:yaml.org,2002:str'209 index += 1210 else:211 index += 1212 if merge:213 node.value = merge + node.value214 215 def construct_mapping(self, node, deep=False):216 if isinstance(node, MappingNode):217 self.flatten_mapping(node)218 return super().construct_mapping(node, deep=deep)219 220 def construct_yaml_null(self, node):221 self.construct_scalar(node)222 return None223 224 bool_values = {225 'yes': True,226 'no': False,227 'true': True,228 'false': False,229 'on': True,230 'off': False,231 }232 233 def construct_yaml_bool(self, node):234 value = self.construct_scalar(node)235 return self.bool_values[value.lower()]236 237 def construct_yaml_int(self, node):238 value = self.construct_scalar(node)239 value = value.replace('_', '')240 sign = +1241 if value[0] == '-':242 sign = -1243 if value[0] in '+-':244 value = value[1:]245 if value == '0':246 return 0247 elif value.startswith('0b'):248 return sign*int(value[2:], 2)249 elif value.startswith('0x'):250 return sign*int(value[2:], 16)251 elif value[0] == '0':252 return sign*int(value, 8)253 elif ':' in value:254 digits = [int(part) for part in value.split(':')]255 digits.reverse()256 base = 1257 value = 0258 for digit in digits:259 value += digit*base260 base *= 60261 return sign*value262 else:263 return sign*int(value)264 265 inf_value = 1e300266 while inf_value != inf_value*inf_value:267 inf_value *= inf_value268 nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99).269 270 def construct_yaml_float(self, node):271 value = self.construct_scalar(node)272 value = value.replace('_', '').lower()273 sign = +1274 if value[0] == '-':275 sign = -1276 if value[0] in '+-':277 value = value[1:]278 if value == '.inf':279 return sign*self.inf_value280 elif value == '.nan':281 return self.nan_value282 elif ':' in value:283 digits = [float(part) for part in value.split(':')]284 digits.reverse()285 base = 1286 value = 0.0287 for digit in digits:288 value += digit*base289 base *= 60290 return sign*value291 else:292 return sign*float(value)293 294 def construct_yaml_binary(self, node):295 try:296 value = self.construct_scalar(node).encode('ascii')297 except UnicodeEncodeError as exc:298 raise ConstructorError(None, None,299 "failed to convert base64 data into ascii: %s" % exc,300 node.start_mark)301 try:302 if hasattr(base64, 'decodebytes'):303 return base64.decodebytes(value)304 else:305 return base64.decodestring(value)306 except binascii.Error as exc:307 raise ConstructorError(None, None,308 "failed to decode base64 data: %s" % exc, node.start_mark)309 310 timestamp_regexp = re.compile(311 r'''^(?P<year>[0-9][0-9][0-9][0-9])312 -(?P<month>[0-9][0-9]?)313 -(?P<day>[0-9][0-9]?)314 (?:(?:[Tt]|[ \t]+)315 (?P<hour>[0-9][0-9]?)316 :(?P<minute>[0-9][0-9])317 :(?P<second>[0-9][0-9])318 (?:\.(?P<fraction>[0-9]*))?319 (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)320 (?::(?P<tz_minute>[0-9][0-9]))?))?)?$''', re.X)321 322 def construct_yaml_timestamp(self, node):323 value = self.construct_scalar(node)324 match = self.timestamp_regexp.match(node.value)325 values = match.groupdict()326 year = int(values['year'])327 month = int(values['month'])328 day = int(values['day'])329 if not values['hour']:330 return datetime.date(year, month, day)331 hour = int(values['hour'])332 minute = int(values['minute'])333 second = int(values['second'])334 fraction = 0335 tzinfo = None336 if values['fraction']:337 fraction = values['fraction'][:6]338 while len(fraction) < 6:339 fraction += '0'340 fraction = int(fraction)341 if values['tz_sign']:342 tz_hour = int(values['tz_hour'])343 tz_minute = int(values['tz_minute'] or 0)344 delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute)345 if values['tz_sign'] == '-':346 delta = -delta347 tzinfo = datetime.timezone(delta)348 elif values['tz']:349 tzinfo = datetime.timezone.utc350 return datetime.datetime(year, month, day, hour, minute, second, fraction,351 tzinfo=tzinfo)352 353 def construct_yaml_omap(self, node):354 # Note: we do not check for duplicate keys, because it's too355 # CPU-expensive.356 omap = []357 yield omap358 if not isinstance(node, SequenceNode):359 raise ConstructorError("while constructing an ordered map", node.start_mark,360 "expected a sequence, but found %s" % node.id, node.start_mark)361 for subnode in node.value:362 if not isinstance(subnode, MappingNode):363 raise ConstructorError("while constructing an ordered map", node.start_mark,364 "expected a mapping of length 1, but found %s" % subnode.id,365 subnode.start_mark)366 if len(subnode.value) != 1:367 raise ConstructorError("while constructing an ordered map", node.start_mark,368 "expected a single mapping item, but found %d items" % len(subnode.value),369 subnode.start_mark)370 key_node, value_node = subnode.value[0]371 key = self.construct_object(key_node)372 value = self.construct_object(value_node)373 omap.append((key, value))374 375 def construct_yaml_pairs(self, node):376 # Note: the same code as `construct_yaml_omap`.377 pairs = []378 yield pairs379 if not isinstance(node, SequenceNode):380 raise ConstructorError("while constructing pairs", node.start_mark,381 "expected a sequence, but found %s" % node.id, node.start_mark)382 for subnode in node.value:383 if not isinstance(subnode, MappingNode):384 raise ConstructorError("while constructing pairs", node.start_mark,385 "expected a mapping of length 1, but found %s" % subnode.id,386 subnode.start_mark)387 if len(subnode.value) != 1:388 raise ConstructorError("while constructing pairs", node.start_mark,389 "expected a single mapping item, but found %d items" % len(subnode.value),390 subnode.start_mark)391 key_node, value_node = subnode.value[0]392 key = self.construct_object(key_node)393 value = self.construct_object(value_node)394 pairs.append((key, value))395 396 def construct_yaml_set(self, node):397 data = set()398 yield data399 value = self.construct_mapping(node)400 data.update(value)401 402 def construct_yaml_str(self, node):403 return self.construct_scalar(node)404 405 def construct_yaml_seq(self, node):406 data = []407 yield data408 data.extend(self.construct_sequence(node))409 410 def construct_yaml_map(self, node):411 data = {}412 yield data413 value = self.construct_mapping(node)414 data.update(value)415 416 def construct_yaml_object(self, node, cls):417 data = cls.__new__(cls)418 yield data419 if hasattr(data, '__setstate__'):420 state = self.construct_mapping(node, deep=True)421 data.__setstate__(state)422 else:423 state = self.construct_mapping(node)424 data.__dict__.update(state)425 426 def construct_undefined(self, node):427 raise ConstructorError(None, None,428 "could not determine a constructor for the tag %r" % node.tag,429 node.start_mark)430 431SafeConstructor.add_constructor(432 'tag:yaml.org,2002:null',433 SafeConstructor.construct_yaml_null)434 435SafeConstructor.add_constructor(436 'tag:yaml.org,2002:bool',437 SafeConstructor.construct_yaml_bool)438 439SafeConstructor.add_constructor(440 'tag:yaml.org,2002:int',441 SafeConstructor.construct_yaml_int)442 443SafeConstructor.add_constructor(444 'tag:yaml.org,2002:float',445 SafeConstructor.construct_yaml_float)446 447SafeConstructor.add_constructor(448 'tag:yaml.org,2002:binary',449 SafeConstructor.construct_yaml_binary)450 451SafeConstructor.add_constructor(452 'tag:yaml.org,2002:timestamp',453 SafeConstructor.construct_yaml_timestamp)454 455SafeConstructor.add_constructor(456 'tag:yaml.org,2002:omap',457 SafeConstructor.construct_yaml_omap)458 459SafeConstructor.add_constructor(460 'tag:yaml.org,2002:pairs',461 SafeConstructor.construct_yaml_pairs)462 463SafeConstructor.add_constructor(464 'tag:yaml.org,2002:set',465 SafeConstructor.construct_yaml_set)466 467SafeConstructor.add_constructor(468 'tag:yaml.org,2002:str',469 SafeConstructor.construct_yaml_str)470 471SafeConstructor.add_constructor(472 'tag:yaml.org,2002:seq',473 SafeConstructor.construct_yaml_seq)474 475SafeConstructor.add_constructor(476 'tag:yaml.org,2002:map',477 SafeConstructor.construct_yaml_map)478 479SafeConstructor.add_constructor(None,480 SafeConstructor.construct_undefined)481 482class FullConstructor(SafeConstructor):483 # 'extend' is blacklisted because it is used by484 # construct_python_object_apply to add `listitems` to a newly generate485 # python instance486 def get_state_keys_blacklist(self):487 return ['^extend$', '^__.*__$']488 489 def get_state_keys_blacklist_regexp(self):490 if not hasattr(self, 'state_keys_blacklist_regexp'):491 self.state_keys_blacklist_regexp = re.compile('(' + '|'.join(self.get_state_keys_blacklist()) + ')')492 return self.state_keys_blacklist_regexp493 494 def construct_python_str(self, node):495 return self.construct_scalar(node)496 497 def construct_python_unicode(self, node):498 return self.construct_scalar(node)499 500 def construct_python_bytes(self, node):501 try:502 value = self.construct_scalar(node).encode('ascii')503 except UnicodeEncodeError as exc:504 raise ConstructorError(None, None,505 "failed to convert base64 data into ascii: %s" % exc,506 node.start_mark)507 try:508 if hasattr(base64, 'decodebytes'):509 return base64.decodebytes(value)510 else:511 return base64.decodestring(value)512 except binascii.Error as exc:513 raise ConstructorError(None, None,514 "failed to decode base64 data: %s" % exc, node.start_mark)515 516 def construct_python_long(self, node):517 return self.construct_yaml_int(node)518 519 def construct_python_complex(self, node):520 return complex(self.construct_scalar(node))521 522 def construct_python_tuple(self, node):523 return tuple(self.construct_sequence(node))524 525 def find_python_module(self, name, mark, unsafe=False):526 if not name:527 raise ConstructorError("while constructing a Python module", mark,528 "expected non-empty name appended to the tag", mark)529 if unsafe:530 try:531 __import__(name)532 except ImportError as exc:533 raise ConstructorError("while constructing a Python module", mark,534 "cannot find module %r (%s)" % (name, exc), mark)535 if name not in sys.modules:536 raise ConstructorError("while constructing a Python module", mark,537 "module %r is not imported" % name, mark)538 return sys.modules[name]539 540 def find_python_name(self, name, mark, unsafe=False):541 if not name:542 raise ConstructorError("while constructing a Python object", mark,543 "expected non-empty name appended to the tag", mark)544 if '.' in name:545 module_name, object_name = name.rsplit('.', 1)546 else:547 module_name = 'builtins'548 object_name = name549 if unsafe:550 try:551 __import__(module_name)552 except ImportError as exc:553 raise ConstructorError("while constructing a Python object", mark,554 "cannot find module %r (%s)" % (module_name, exc), mark)555 if module_name not in sys.modules:556 raise ConstructorError("while constructing a Python object", mark,557 "module %r is not imported" % module_name, mark)558 module = sys.modules[module_name]559 if not hasattr(module, object_name):560 raise ConstructorError("while constructing a Python object", mark,561 "cannot find %r in the module %r"562 % (object_name, module.__name__), mark)563 return getattr(module, object_name)564 565 def construct_python_name(self, suffix, node):566 value = self.construct_scalar(node)567 if value:568 raise ConstructorError("while constructing a Python name", node.start_mark,569 "expected the empty value, but found %r" % value, node.start_mark)570 return self.find_python_name(suffix, node.start_mark)571 572 def construct_python_module(self, suffix, node):573 value = self.construct_scalar(node)574 if value:575 raise ConstructorError("while constructing a Python module", node.start_mark,576 "expected the empty value, but found %r" % value, node.start_mark)577 return self.find_python_module(suffix, node.start_mark)578 579 def make_python_instance(self, suffix, node,580 args=None, kwds=None, newobj=False, unsafe=False):581 if not args:582 args = []583 if not kwds:584 kwds = {}585 cls = self.find_python_name(suffix, node.start_mark)586 if not (unsafe or isinstance(cls, type)):587 raise ConstructorError("while constructing a Python instance", node.start_mark,588 "expected a class, but found %r" % type(cls),589 node.start_mark)590 if newobj and isinstance(cls, type):591 return cls.__new__(cls, *args, **kwds)592 else:593 return cls(*args, **kwds)594 595 def set_python_instance_state(self, instance, state, unsafe=False):596 if hasattr(instance, '__setstate__'):597 instance.__setstate__(state)598 else:599 slotstate = {}600 if isinstance(state, tuple) and len(state) == 2:601 state, slotstate = state602 if hasattr(instance, '__dict__'):603 if not unsafe and state:604 for key in state.keys():605 self.check_state_key(key)606 instance.__dict__.update(state)607 elif state:608 slotstate.update(state)609 for key, value in slotstate.items():610 if not unsafe:611 self.check_state_key(key)612 setattr(instance, key, value)613 614 def construct_python_object(self, suffix, node):615 # Format:616 # !!python/object:module.name { ... state ... }617 instance = self.make_python_instance(suffix, node, newobj=True)618 yield instance619 deep = hasattr(instance, '__setstate__')620 state = self.construct_mapping(node, deep=deep)621 self.set_python_instance_state(instance, state)622 623 def construct_python_object_apply(self, suffix, node, newobj=False):624 # Format:625 # !!python/object/apply # (or !!python/object/new)626 # args: [ ... arguments ... ]627 # kwds: { ... keywords ... }628 # state: ... state ...629 # listitems: [ ... listitems ... ]630 # dictitems: { ... dictitems ... }631 # or short format:632 # !!python/object/apply [ ... arguments ... ]633 # The difference between !!python/object/apply and !!python/object/new634 # is how an object is created, check make_python_instance for details.635 if isinstance(node, SequenceNode):636 args = self.construct_sequence(node, deep=True)637 kwds = {}638 state = {}639 listitems = []640 dictitems = {}641 else:642 value = self.construct_mapping(node, deep=True)643 args = value.get('args', [])644 kwds = value.get('kwds', {})645 state = value.get('state', {})646 listitems = value.get('listitems', [])647 dictitems = value.get('dictitems', {})648 instance = self.make_python_instance(suffix, node, args, kwds, newobj)649 if state:650 self.set_python_instance_state(instance, state)651 if listitems:652 instance.extend(listitems)653 if dictitems:654 for key in dictitems:655 instance[key] = dictitems[key]656 return instance657 658 def construct_python_object_new(self, suffix, node):659 return self.construct_python_object_apply(suffix, node, newobj=True)660 661FullConstructor.add_constructor(662 'tag:yaml.org,2002:python/none',663 FullConstructor.construct_yaml_null)664 665FullConstructor.add_constructor(666 'tag:yaml.org,2002:python/bool',667 FullConstructor.construct_yaml_bool)668 669FullConstructor.add_constructor(670 'tag:yaml.org,2002:python/str',671 FullConstructor.construct_python_str)672 673FullConstructor.add_constructor(674 'tag:yaml.org,2002:python/unicode',675 FullConstructor.construct_python_unicode)676 677FullConstructor.add_constructor(678 'tag:yaml.org,2002:python/bytes',679 FullConstructor.construct_python_bytes)680 681FullConstructor.add_constructor(682 'tag:yaml.org,2002:python/int',683 FullConstructor.construct_yaml_int)684 685FullConstructor.add_constructor(686 'tag:yaml.org,2002:python/long',687 FullConstructor.construct_python_long)688 689FullConstructor.add_constructor(690 'tag:yaml.org,2002:python/float',691 FullConstructor.construct_yaml_float)692 693FullConstructor.add_constructor(694 'tag:yaml.org,2002:python/complex',695 FullConstructor.construct_python_complex)696 697FullConstructor.add_constructor(698 'tag:yaml.org,2002:python/list',699 FullConstructor.construct_yaml_seq)700 701FullConstructor.add_constructor(702 'tag:yaml.org,2002:python/tuple',703 FullConstructor.construct_python_tuple)704 705FullConstructor.add_constructor(706 'tag:yaml.org,2002:python/dict',707 FullConstructor.construct_yaml_map)708 709FullConstructor.add_multi_constructor(710 'tag:yaml.org,2002:python/name:',711 FullConstructor.construct_python_name)712 713class UnsafeConstructor(FullConstructor):714 715 def find_python_module(self, name, mark):716 return super(UnsafeConstructor, self).find_python_module(name, mark, unsafe=True)717 718 def find_python_name(self, name, mark):719 return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True)720 721 def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False):722 return super(UnsafeConstructor, self).make_python_instance(723 suffix, node, args, kwds, newobj, unsafe=True)724 725 def set_python_instance_state(self, instance, state):726 return super(UnsafeConstructor, self).set_python_instance_state(727 instance, state, unsafe=True)728 729UnsafeConstructor.add_multi_constructor(730 'tag:yaml.org,2002:python/module:',731 UnsafeConstructor.construct_python_module)732 733UnsafeConstructor.add_multi_constructor(734 'tag:yaml.org,2002:python/object:',735 UnsafeConstructor.construct_python_object)736 737UnsafeConstructor.add_multi_constructor(738 'tag:yaml.org,2002:python/object/new:',739 UnsafeConstructor.construct_python_object_new)740 741UnsafeConstructor.add_multi_constructor(742 'tag:yaml.org,2002:python/object/apply:',743 UnsafeConstructor.construct_python_object_apply)744 745# Constructor is same as UnsafeConstructor. Need to leave this in place in case746# people have extended it directly.747class Constructor(UnsafeConstructor):748 pass749 