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
|
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import with_statement
import functools
from twisted.internet import defer
from txtorcon.interface import ITorControlProtocol
class MagicContainer(object):
"""
This merely contains 1 or more methods or further MagicContainer
instances; see _do_setup in TorInfo.
Once _setup_complete() is called, this behaves differently so that
one can get nicer access to GETINFO things from TorInfo --
specifically dir() and so forth pretend that there are only
methods/attributes that pertain to actual Tor GETINFO keys.
See TorInfo.
"""
def __init__(self, n):
self._txtorcon_name = n
self.attrs = {}
self._setup = False
def _setup_complete(self):
self._setup = True
def _add_attribute(self, n, v):
self.attrs[n] = v
def __repr__(self):
return object.__getattribute__(self, '_txtorcon_name')
def __getitem__(self, idx):
return list(object.__getattribute__(self, 'attrs').items())[idx][1]
def __len__(self):
return len(object.__getattribute__(self, 'attrs'))
def __dir__(self):
return list(object.__getattribute__(self, 'attrs').keys())
def __getattribute__(self, name):
sup = super(MagicContainer, self)
if sup.__getattribute__('_setup') is False:
return sup.__getattribute__(name)
attrs = sup.__getattribute__('attrs')
if name == '__members__':
return list(attrs.keys())
else:
if name.startswith('__'):
return sup.__getattribute__(name)
try:
return attrs[name]
except KeyError:
if name in ['dump']:
return object.__getattribute__(self, name)
raise AttributeError(name)
def dump(self, prefix):
prefix = prefix + '.' + object.__getattribute__(self, '_txtorcon_name')
for x in list(object.__getattribute__(self, 'attrs').values()):
x.dump(prefix)
class ConfigMethod(object):
def __init__(self, info_key, protocol, takes_arg=False):
self.info_key = info_key
self.proto = protocol
self.takes_arg = takes_arg
def dump(self, prefix):
n = self.info_key.replace('/', '.')
n = n.replace('-', '_')
s = '%s(%s)' % (n, 'arg' if self.takes_arg else '')
return s
def __call__(self, *args):
if self.takes_arg:
if len(args) != 1:
raise TypeError(
'"%s" takes exactly one argument' % self.info_key
)
req = '%s/%s' % (self.info_key, str(args[0]))
else:
if len(args) != 0:
raise TypeError('"%s" takes no arguments' % self.info_key)
req = self.info_key
def stripper(key, arg):
# strip "keyname="
# sometimes keyname= is followed by a newline, so final .strip()
return arg.strip()[len(key) + 1:].strip()
d = self.proto.get_info_raw(req)
d.addCallback(functools.partial(stripper, req))
return d
def __str__(self):
arg = ''
if self.takes_arg:
arg = 'arg'
return '%s(%s)' % (self.info_key.replace('-', '_'), arg)
class TorInfo(object):
"""Implements some attribute magic over top of TorControlProtocol so
that all the available GETINFO values are gettable in a little
easier fashion. Dashes are replaced by underscores (since dashes
aren't valid in method/attribute names for Python). Some of the
magic methods will take a single string argument if the
corresponding Tor GETINFO would take one (in 'GETINFO info/names'
it will end with '/*', and the same in torspec). In either case,
the method returns a Deferred which will callback with the
requested value, always a string.
For example (see also examples/tor_info.py):
proto = TorControlProtocol()
#...
def cb(arg):
print arg
info = TorInfo(proto)
info.traffic.written().addCallback(cb)
info.ip_to_country('8.8.8.8').addCallback(cb)
For interactive use -- or even checking things progammatically --
TorInfo pretends it only has attributes that coorespond to valid
GETINFO calls. So for example, dir(info) will only return all the
currently valid top-level things. In the above example this might
be ['traffic', 'ip_to_country'] (of course in practice this is a
much longer list). And "dir(info.traffic)" might return ['read',
'written']
For something similar to this for configuration (GETCONF, SETCONF)
see TorConfig which is quite a lot more complicated (internally)
since you can set config items.
NOTE that 'GETINFO config/*' is not supported as it's the only
case that's not a leaf, but theoretically a method.
"""
def __init__(self, control, errback=None):
self._setup = False
self.attrs = {}
'''After _setup is True, these are all we show as attributes.'''
self.protocol = ITorControlProtocol(control)
self.errback = errback
self.post_bootstrap = defer.Deferred()
if self.protocol.post_bootstrap:
self.protocol.post_bootstrap.addCallback(self.bootstrap)
else:
self.bootstrap()
def _add_attribute(self, n, v):
self.attrs[n] = v
# iterator protocol
def __getitem__(self, idx):
sup = super(TorInfo, self)
if sup.__getattribute__('_setup') is True:
return list(object.__getattribute__(self, 'attrs').items())[idx][1]
raise TypeError("No __getitem__ until we've setup.")
def __len__(self):
sup = super(TorInfo, self)
if sup.__getattribute__('_setup') is True:
return len(object.__getattribute__(self, 'attrs'))
raise TypeError("No length until we're setup.")
# change our attribute behavior based on the value of _setup
def __dir__(self):
sup = super(TorInfo, self)
if sup.__getattribute__('_setup') is True:
return list(sup.__getattribute__('attrs').keys())
return list(sup.__getattribute__('__dict__').keys())
def __getattribute__(self, name):
sup = super(TorInfo, self)
if sup.__getattribute__('_setup') is False:
return sup.__getattribute__(name)
attrs = sup.__getattribute__('attrs')
if name == '__members__':
return list(attrs.keys())
else:
try:
return attrs[name]
except KeyError:
if name == 'dump':
return object.__getattribute__(self, name)
raise AttributeError(name)
def bootstrap(self, *args):
d = self.protocol.get_info_raw("info/names")
d.addCallback(self._do_setup)
if self.errback:
d.addErrback(self.errback)
d.addCallback(self._setup_complete)
return d
def dump(self):
for x in object.__getattribute__(self, 'attrs').values():
x.dump('')
def _do_setup(self, data):
# FIXME figure out why network-status doesn't work (get
# nothing back from Tor it seems, although stem does get an
# answer). this is a space-separated list of ~2500 OR id's;
# could it be that LineReceiver can't handle it?
added_magic = []
for line in data.split('\n'):
if line == "info/names=" or line.strip() == '':
continue
(name, documentation) = line.split(' ', 1)
# FIXME think about this -- this is the only case where
# there's something that's a directory
# (i.e. MagicContainer) AND needs to be a ConfigMethod as
# well...but doesn't really seem very useful. Somewhat
# simpler to not support this case for now...
if name == 'config/*':
continue
if name.endswith('/*'):
# this takes an arg, so make a method
bits = name[:-2].split('/')
takes_arg = True
else:
bits = name.split('/')
takes_arg = False
mine = self
for bit in bits[:-1]:
bit = bit.replace('-', '_')
if bit in mine.attrs:
mine = mine.attrs[bit]
if not isinstance(mine, MagicContainer):
raise RuntimeError(
"Already had something: %s for %s" % (bit, name)
)
else:
c = MagicContainer(bit)
added_magic.append(c)
mine._add_attribute(bit, c)
mine = c
n = bits[-1].replace('-', '_')
if n in mine.attrs:
raise RuntimeError(
"Already had something: %s for %s" % (n, name)
)
mine._add_attribute(n, ConfigMethod('/'.join(bits),
self.protocol, takes_arg))
for c in added_magic:
c._setup_complete()
return None
def _setup_complete(self, *args):
pb = self.post_bootstrap
self._setup = True
pb.callback(self)
|