diff options
Diffstat (limited to 'sploit/types')
-rw-r--r-- | sploit/types/__init__.py | 3 | ||||
-rw-r--r-- | sploit/types/index_entry.py | 44 | ||||
-rw-r--r-- | sploit/types/indextbl.py | 103 | ||||
-rw-r--r-- | sploit/types/lict.py | 202 |
4 files changed, 352 insertions, 0 deletions
diff --git a/sploit/types/__init__.py b/sploit/types/__init__.py new file mode 100644 index 0000000..a618162 --- /dev/null +++ b/sploit/types/__init__.py @@ -0,0 +1,3 @@ +from .indextbl import * +from .index_entry import * +from .lict import * diff --git a/sploit/types/index_entry.py b/sploit/types/index_entry.py new file mode 100644 index 0000000..a03ab92 --- /dev/null +++ b/sploit/types/index_entry.py @@ -0,0 +1,44 @@ +import copy + +class IndexEntry: + """ + Generic IndexTbl entry object + + IndexEntry is intended to be subclassed to create interesting types that are + compatible with IndexTbl directories. IndexEntry gives objects a baseline + int-like personality. + + IndexEntry objects are convertable to int via int(), bin(), hex(), etc. This + integer value is manipulated via the object's "base" property, and a few + operators are implemented to provide nicer syntax for this as well. The use + of operators generally yield distinct copies of the original object. + + The property name "base" is used since it has semantic meaning for the + IndexTbl class, which is itself an extension of this class. + + base (int): Index integer value + """ + + base = 0 + + def __init__(self, base=0): + """Construct index with the given base value.""" + self.base = base + + def __index__(self): + """Convert index to base integer value.""" + return int(self.base) + + def __matmul__(self, base): + """Create new object with the given base value.""" + new = copy.copy(self) + new.base = base + return new + + def __add__(self, add): + """Create new object with the given relative base value.""" + return self @ (self.base + add) + + def __sub__(self, sub): + """Create new object with the given relative base value.""" + return self @ (self.base - sub) diff --git a/sploit/types/indextbl.py b/sploit/types/indextbl.py new file mode 100644 index 0000000..4f57a59 --- /dev/null +++ b/sploit/types/indextbl.py @@ -0,0 +1,103 @@ +from abc import abstractmethod +from collections.abc import Collection + +from sploit.types.index_entry import IndexEntry + +class IndexTbl(IndexEntry, Collection): + """ + Abstract Index Table + + IndexTbl is a common interface to an abstracted key-value store. The + storage mechanism as well as lookup semantics are defined by concrete + implementations. "Index" in this case is more akin to a directory index or + book index than a strictly numeric array index. + + In general, concrete tables may store values of any or multiple different + types. In particular, tables should give special accommodation for values + of type IndexEntry. These objects usually represent "rich" versions of the + nominal data types the table expects to contain. Implementation repr() + methods usually also annotate which members are IndexEntries. + + IndexTbl extends from IndexEntry, and so has a base value which represents + the "base index" of the table. The meaning of this depends on the + implementation. This inheritance also means that tables are generally + expected to be nestable. + + IndexTbls allow indices to be accessed via attribute or subscript notation. + This is probably the key characteristic feature of the class. The class + namespace is kept as clean as possible to make for the fewest collisions + between index names and other (real) class attributes. Note that there are + abstract methods, required to be overridden, which implement the index + access. These methods are only called for normal indices, not the table + base. + + Because this class overrides attribute access, normal automatic object + copying is broken. Because of this, implementations must also provide a + definition for the __copy__() method as well. + """ + + @abstractmethod + def __getindex__(self, index): + """Lookup and retrieve index value.""" + raise NotImplementedError + + @abstractmethod + def __setindex__(self, index, value): + """Lookup and set index value.""" + raise NotImplementedError + + @abstractmethod + def __delindex__(self, index): + """Lookup and delete index value.""" + raise NotImplementedError + + @abstractmethod + def __copy__(self): + """Create a copy of this IndexTbl object.""" + raise NotImplementedError + + def __contains__(self, index): + """Test the existence of the given index.""" + try: + self.__getindex__(index) + except KeyError: + return False + else: + return True + + # Attribute access methods + + def __getattr__(self, index): + """Get value via attribute.""" + return self[index] + + def __setattr__(self, index, value): + """Set value via attribute.""" + self[index] = value + + def __delattr__(self, index): + """Delete value via attribute.""" + del self[index] + + # Subscript/item access methods + + def __getitem__(self, index): + """Get value via subscript.""" + if index == "base": + return self.base + return self.__getindex__(index) + + def __setitem__(self, index, value): + """Set value via subscript.""" + if index == "base": + object.__setattr__(self, "base", value) + elif index in dir(self): + raise KeyError(f"IndexTbl: Index is reserved: {index}") + else: + self.__setindex__(index, value) + + def __delitem__(self, index): + """Delete value via subscript.""" + if index == "base": + raise KeyError("IndexTbl: May not delete index: base") + self.__delindex__(index) diff --git a/sploit/types/lict.py b/sploit/types/lict.py new file mode 100644 index 0000000..ab6cb1f --- /dev/null +++ b/sploit/types/lict.py @@ -0,0 +1,202 @@ +from collections.abc import MutableSequence, MutableMapping + +class Lict(MutableSequence, MutableMapping): + """ + List / dictionary hybrid container + + Lict attempts to provide an API for a list which supports optional element + keys in addition to normal positional indices. For Lict, index types are + int and slice. Keys may be any other type except for None, as None + indicates an unkeyed value. + + Nearly all of the operations you'd except to perform on list or dict are + implemented here. However, in cases of conflict, the list behavior is + usually preferred (for example: iter(Lict) iterates over values instead of + keys). In general, Licts are slightly more list-like, since keys are + optional, but sequence is required. + + Licts can be constructed from any iterable, including other Licts. When + constructing from mappings, similar heuristics as dict's are used to parse + key values. + + In addition to keys and indices, slices may be used to return, modify, or + delete a portion of a Lict. The start and end fields of a slice may be + either keys or indices, however the step field must be an integer as usual. + + When assigning to a non-existent key, the new element is sequentially + inserted at the end of the Lict. + """ + + def __init__(self, values=None): + """Construct new Lict, optionally populated by the given iterable.""" + self.__keys = [] + self.__vals = [] + if values is not None: + self.extend(values) + + def __repr__(self): + """Return human-readable Lict.""" + s = "" + for i in range(len(self)): + if self.__keys[i] is not None: + s += f"{repr(self.__keys[i])}: " + s += f"{repr(self.__vals[i])}, " + return f"{{[{s[:-2]}]}}" + + def __copy__(self): + """Return shallow copy of object.""" + return self.copy() + + def copy(self): + """Return shallow copy of object.""" + return Lict(self) + + def key2idx(self, arg): + """ + Return value of index type for the given input. + + For keys, return the corresponding index, or raise KeyError. + For slices, return a slice with components converted to index type. + + If arg is already an index (or None) it is returned as-is with no + assertions made. + """ + if isinstance(arg, slice): + return slice(self.key2idx(arg.start), self.key2idx(arg.stop), arg.step) + if isinstance(arg, int) or arg is None: + return arg + try: + return self.__keys.index(arg) + except ValueError as ex: + raise KeyError(f"Lict: Key does not exist: {arg}") from ex + + def idx2key(self, arg): + """ + Return value of key type for the given input. + + For indices, return the corresponding key, None, or raise IndexError. + For slices, return a slice with components converted to key type. + + If arg is already a key type (or None) it is returned as-is with no + assertions made. + """ + if isinstance(arg, slice): + return slice(self.idx2key(arg.start), self.idx2key(arg.stop), arg.step) + if not isinstance(arg, int) or arg is None: + return arg + return self.__keys[arg] + + def haskey(self, arg): + """ + Test existence of key in Lict object. + + `x in Lict` only tests for the _value_ x in Lict. This is consistent + with list behavior. This method is provided to test keys as well. + Raises TypeError on any index type. + """ + if arg is None: + return False + if isinstance(arg, (int, slice)): + raise TypeError(f"Lict: Unsupported key type: {type(arg)}") + return arg in self.__keys + + def __assign_slice(self, i, value): + """Update Lict values according to element slice.""" + value = Lict(value) + tmp = self.copy() + tmp.__keys[i] = value.__keys + tmp.__vals[i] = value.__vals + + check_keys = [ x for x in tmp.__keys if x is not None ] + if len(check_keys) != len(set(check_keys)): + raise ValueError("Lict: Slice assignment results in duplicate keys") + + self.__keys = tmp.__keys + self.__vals = tmp.__vals + + # collections.abc abstract methods + + def __len__(self): + """Return number of elements in Lict.""" + assert len(self.__keys) == len(self.__vals) + return len(self.__keys) + + def __getitem__(self, arg): + """Return value for given index, key, or slice.""" + i = self.key2idx(arg) + if isinstance(i, slice): + return Lict(zip(self.__keys[i], self.__vals[i])) + return self.__vals[i] + + def __setitem__(self, arg, value): + """Set value for given index, key, or slice.""" + try: + i = self.key2idx(arg) + except KeyError: + self.append(value, arg) + else: + if isinstance(i, slice): + self.__assign_slice(i, value) + else: + self.__vals[i] = value + + def __delitem__(self, arg): + """Delete value for given index, key, or slice.""" + i = self.key2idx(arg) + del self.__keys[i] + del self.__vals[i] + + def insert(self, where, value, key=None): + """Insert value into Lict. Optionally apply the given key.""" + if self.haskey(key): + raise KeyError(f"Lict: Key is not unique: {key}") + i = self.key2idx(where) + self.__keys.insert(i, key) + self.__vals.insert(i, value) + + # Sequence overrides + + def append(self, value, key=None): + """Append value to Lict. Optionally apply the given key.""" + self.insert(len(self), value, key) + + def extend(self, values): + """Append all values in given iterable to the Lict.""" + try: values = [ [k, v] for k, v in values.items() ] + except: + try: values = [ [k, v] for k, v in iter(values) ] + except: values = [ [None, v] for v in iter(values) ] + for k, v in values: + self.append(v, k) + + def reverse(self): + """Reverse the sequence of Lict in-place. Keys follow values.""" + self.__keys.reverse() + self.__vals.reverse() + + # Mapping overrides + + def get(self, key, default=None): + """Return value for given key, or default if unable.""" + try: return self[key] + except: return default + + def popitem(self): + """Pop a key-value pair from the Lict and return it.""" + return (self.__keys.pop(), self.__vals.pop()) + + def items(self): + """Return an iterable of key-value pairs.""" + return list(zip(self.__keys, self.__vals)) + + def keys(self): + """Return an iterable of the Lict's keys.""" + return [ x for x in self.__keys if x is not None ] + + def values(self): + """Return an iterable of the Lict's values.""" + return list(self.__vals) + + def update(self, values): + """Method is unsupported.""" + raise NotImplementedError |