summaryrefslogtreecommitdiffstats
path: root/sploit/types
diff options
context:
space:
mode:
Diffstat (limited to 'sploit/types')
-rw-r--r--sploit/types/__init__.py3
-rw-r--r--sploit/types/index_entry.py44
-rw-r--r--sploit/types/indextbl.py103
-rw-r--r--sploit/types/lict.py202
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