PK EN6 ipfsapi/__init__.py"""Python IPFS API client library"""
from __future__ import absolute_import
import warnings
warnings.warn(
"The `ipfsapi` library is deprecated and will stop receiving updates on "
"the 31.12.2019! If you are on Python 3.5+ please enable and fix all "
"Python deprecation warnings (CPython flag `-Wd`) and switch to the new "
"`ipfshttpclient` library name. Python 2.7 and 3.4 will not be supported "
"by the new library, so please upgrade.", FutureWarning, stacklevel=2
)
from .version import __version__
###########################
# Import stable API parts #
###########################
from . import exceptions
from .client import DEFAULT_HOST, DEFAULT_PORT, DEFAULT_BASE
from .client import VERSION_MINIMUM, VERSION_MAXIMUM
from .client import Client, assert_version, connect
PK ENS!<. <. ipfsapi/encoding.py# -*- encoding: utf-8 -*-
"""Defines encoding related classes.
.. note::
The XML and ProtoBuf encoders are currently not functional.
"""
from __future__ import absolute_import
import abc
import codecs
import io
import json
import pickle
import six
from . import exceptions
class Encoding(object):
"""Abstract base for a data parser/encoder interface.
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def parse_partial(self, raw):
"""Parses the given data and yields all complete data sets that can
be built from this.
Raises
------
~ipfsapi.exceptions.DecodingError
Parameters
----------
raw : bytes
Data to be parsed
Returns
-------
generator
"""
def parse_finalize(self):
"""Finalizes parsing based on remaining buffered data and yields the
remaining data sets.
Raises
------
~ipfsapi.exceptions.DecodingError
Returns
-------
generator
"""
return ()
def parse(self, raw):
"""Returns a Python object decoded from the bytes of this encoding.
Raises
------
~ipfsapi.exceptions.DecodingError
Parameters
----------
raw : bytes
Data to be parsed
Returns
-------
object
"""
results = list(self.parse_partial(raw))
results.extend(self.parse_finalize())
return results[0] if len(results) == 1 else results
@abc.abstractmethod
def encode(self, obj):
"""Serialize a raw object into corresponding encoding.
Raises
------
~ipfsapi.exceptions.EncodingError
Parameters
----------
obj : object
Object to be encoded
"""
class Dummy(Encoding):
"""Dummy parser/encoder that does nothing.
"""
name = "none"
def parse_partial(self, raw):
"""Yields the data passed into this method.
Parameters
----------
raw : bytes
Any kind of data
Returns
-------
generator
"""
yield raw
def encode(self, obj):
"""Returns the bytes representation of the data passed into this
function.
Parameters
----------
obj : object
Any Python object
Returns
-------
bytes
"""
return six.b(str(obj))
class Json(Encoding):
"""JSON parser/encoder that handles concatenated JSON.
"""
name = 'json'
def __init__(self):
self._buffer = []
self._decoder1 = codecs.getincrementaldecoder('utf-8')()
self._decoder2 = json.JSONDecoder()
self._lasterror = None
def parse_partial(self, data):
"""Incrementally decodes JSON data sets into Python objects.
Raises
------
~ipfsapi.exceptions.DecodingError
Returns
-------
generator
"""
try:
# Python 3 requires all JSON data to be a text string
lines = self._decoder1.decode(data, False).split("\n")
# Add first input line to last buffer line, if applicable, to
# handle cases where the JSON string has been chopped in half
# at the network level due to streaming
if len(self._buffer) > 0 and self._buffer[-1] is not None:
self._buffer[-1] += lines[0]
self._buffer.extend(lines[1:])
else:
self._buffer.extend(lines)
except UnicodeDecodeError as error:
raise exceptions.DecodingError('json', error)
# Process data buffer
index = 0
try:
# Process each line as separate buffer
#PERF: This way the `.lstrip()` call becomes almost always a NOP
# even if it does return a different string it will only
# have to allocate a new buffer for the currently processed
# line.
while index < len(self._buffer):
while self._buffer[index]:
# Make sure buffer does not start with whitespace
#PERF: `.lstrip()` does not reallocate if the string does
# not actually start with whitespace.
self._buffer[index] = self._buffer[index].lstrip()
# Handle case where the remainder of the line contained
# only whitespace
if not self._buffer[index]:
self._buffer[index] = None
continue
# Try decoding the partial data buffer and return results
# from this
data = self._buffer[index]
for index2 in range(index, len(self._buffer)):
# If decoding doesn't succeed with the currently
# selected buffer (very unlikely with our current
# class of input data) then retry with appending
# any other pending pieces of input data
# This will happen with JSON data that contains
# arbitrary new-lines: "{1:\n2,\n3:4}"
if index2 > index:
data += "\n" + self._buffer[index2]
try:
(obj, offset) = self._decoder2.raw_decode(data)
except ValueError:
# Treat error as fatal if we have already added
# the final buffer to the input
if (index2 + 1) == len(self._buffer):
raise
else:
index = index2
break
# Decoding succeeded – yield result and shorten buffer
yield obj
if offset < len(self._buffer[index]):
self._buffer[index] = self._buffer[index][offset:]
else:
self._buffer[index] = None
index += 1
except ValueError as error:
# It is unfortunately not possible to reliably detect whether
# parsing ended because of an error *within* the JSON string, or
# an unexpected *end* of the JSON string.
# We therefor have to assume that any error that occurs here
# *might* be related to the JSON parser hitting EOF and therefor
# have to postpone error reporting until `parse_finalize` is
# called.
self._lasterror = error
finally:
# Remove all processed buffers
del self._buffer[0:index]
def parse_finalize(self):
"""Raises errors for incomplete buffered data that could not be parsed
because the end of the input data has been reached.
Raises
------
~ipfsapi.exceptions.DecodingError
Returns
-------
tuple : Always empty
"""
try:
try:
# Raise exception for remaining bytes in bytes decoder
self._decoder1.decode(b'', True)
except UnicodeDecodeError as error:
raise exceptions.DecodingError('json', error)
# Late raise errors that looked like they could have been fixed if
# the caller had provided more data
if self._buffer:
raise exceptions.DecodingError('json', self._lasterror)
finally:
# Reset state
self._buffer = []
self._lasterror = None
self._decoder1.reset()
return ()
def encode(self, obj):
"""Returns ``obj`` serialized as JSON formatted bytes.
Raises
------
~ipfsapi.exceptions.EncodingError
Parameters
----------
obj : str | list | dict | int
JSON serializable Python object
Returns
-------
bytes
"""
try:
result = json.dumps(obj, sort_keys=True, indent=None,
separators=(',', ':'))
if isinstance(result, six.text_type):
return result.encode("utf-8")
else:
return result
except (UnicodeEncodeError, TypeError) as error:
raise exceptions.EncodingError('json', error)
class Pickle(Encoding):
"""Python object parser/encoder using `pickle`.
"""
name = 'pickle'
def __init__(self):
self._buffer = io.BytesIO()
def parse_partial(self, raw):
"""Buffers the given data so that the it can be passed to `pickle` in
one go.
This does not actually process the data in smaller chunks, but merely
buffers it until `parse_finalize` is called! This is mostly because
the standard-library module expects the entire data to be available up
front, which is currently always the case for our code anyways.
Parameters
----------
raw : bytes
Data to be buffered
Returns
-------
tuple : An empty tuple
"""
self._buffer.write(raw)
return ()
def parse_finalize(self):
"""Parses the buffered data and yields the result.
Raises
------
~ipfsapi.exceptions.DecodingError
Returns
-------
generator
"""
try:
self._buffer.seek(0, 0)
yield pickle.load(self._buffer)
except pickle.UnpicklingError as error:
raise exceptions.DecodingError('pickle', error)
def parse(self, raw):
r"""Returns a Python object decoded from a pickle byte stream.
.. code-block:: python
>>> p = Pickle()
>>> p.parse(b'(lp0\nI1\naI2\naI3\naI01\naF4.5\naNaF6000.0\na.')
[1, 2, 3, True, 4.5, None, 6000.0]
Raises
------
~ipfsapi.exceptions.DecodingError
Parameters
----------
raw : bytes
Pickle data bytes
Returns
-------
object
"""
return Encoding.parse(self, raw)
def encode(self, obj):
"""Returns ``obj`` serialized as a pickle binary string.
Raises
------
~ipfsapi.exceptions.EncodingError
Parameters
----------
obj : object
Serializable Python object
Returns
-------
bytes
"""
try:
return pickle.dumps(obj)
except pickle.PicklingError as error:
raise exceptions.EncodingError('pickle', error)
class Protobuf(Encoding):
"""Protobuf parser/encoder that handles protobuf."""
name = 'protobuf'
class Xml(Encoding):
"""XML parser/encoder that handles XML."""
name = 'xml'
# encodings supported by the IPFS api (default is JSON)
__encodings = {
Dummy.name: Dummy,
Json.name: Json,
Pickle.name: Pickle,
Protobuf.name: Protobuf,
Xml.name: Xml
}
def get_encoding(name):
"""
Returns an Encoder object for the named encoding
Raises
------
~ipfsapi.exceptions.EncoderMissingError
Parameters
----------
name : str
Encoding name. Supported options:
* ``"none"``
* ``"json"``
* ``"pickle"``
* ``"protobuf"``
* ``"xml"``
"""
try:
return __encodings[name.lower()]()
except KeyError:
raise exceptions.EncoderMissingError(name)
PK ENwF ipfsapi/exceptions.py# -*- coding: utf-8 -*-
"""
The class hierachy for exceptions is::
Error
+-- VersionMismatch
+-- EncoderError
| +-- EncoderMissingError
| +-- EncodingError
| +-- DecodingError
+-- CommunicationError
+-- ProtocolError
+-- StatusError
+-- ErrorResponse
+-- ConnectionError
+-- TimeoutError
"""
# Delegate list of exceptions to `ipfshttpclient`
from ipfshttpclient.exceptions import *
__all__ = [
"Error",
"VersionMismatch",
"EncoderError",
"EncoderMissingError",
"EncodingError",
"DecodingError",
"CommunicationError",
"ProtocolError",
"StatusError",
"ErrorResponse",
"ConnectionError",
"TimeoutError"
]PK mNY$ $ ipfsapi/version.py# _Versioning scheme:_
# The major and minor version of each release correspond to the supported
# IPFS daemon version. The revision number will be updated whenever we make
# a new release for the `py-ipfs-api` client for that daemon version.
#
# Example: The first client version to support the `0.4.x`-series of the IPFS
# HTTP API will have version `0.4.0`, the second version will have version
# `0.4.1` and so on. When IPFS `0.5.0` is released, the first client version
# to support it will also be released as `0.5.0`.
__version__ = "0.4.4"
PK mNO* * ipfsapi/client/__init__.py# -*- coding: utf-8 -*-
"""IPFS API Bindings for Python.
Classes:
* Client – a TCP client for interacting with an IPFS daemon
"""
from __future__ import absolute_import
import functools
import inspect
import os
import re
import warnings
try: #PY3
import urllib.parse
except ImportError: #PY2
class urllib:
import urlparse as parse
import ipfshttpclient
import netaddr
DEFAULT_HOST = str(os.environ.get("PY_IPFSAPI_DEFAULT_HOST", 'localhost'))
DEFAULT_PORT = int(os.environ.get("PY_IPFSAPI_DEFAULT_PORT", 5001))
DEFAULT_BASE = str(os.environ.get("PY_IPFSAPI_DEFAULT_BASE", 'api/v0'))
VERSION_MINIMUM = "0.4.3"
VERSION_MAXIMUM = "0.5.0"
from .. import exceptions, encoding
from . import base
def assert_version(version, minimum=VERSION_MINIMUM, maximum=VERSION_MAXIMUM):
"""Make sure that the given daemon version is supported by this client
version.
Raises
------
~ipfsapi.exceptions.VersionMismatch
Parameters
----------
version : str
The version of an IPFS daemon.
minimum : str
The minimal IPFS version to allow.
maximum : str
The maximum IPFS version to allow.
"""
# Convert version strings to integer tuples
version = list(map(int, version.split('-', 1)[0].split('.')))
minimum = list(map(int, minimum.split('-', 1)[0].split('.')))
maximum = list(map(int, maximum.split('-', 1)[0].split('.')))
if minimum > version or version >= maximum:
raise exceptions.VersionMismatch(version, minimum, maximum)
def connect(host=DEFAULT_HOST, port=DEFAULT_PORT, base=DEFAULT_BASE,
chunk_size=4096, **defaults):
"""Create a new :class:`~ipfsapi.Client` instance and connect to the
daemon to validate that its version is supported.
Raises
------
~ipfsapi.exceptions.VersionMismatch
~ipfsapi.exceptions.ErrorResponse
~ipfsapi.exceptions.ConnectionError
~ipfsapi.exceptions.ProtocolError
~ipfsapi.exceptions.StatusError
~ipfsapi.exceptions.TimeoutError
All parameters are identical to those passed to the constructor of the
:class:`~ipfsapi.Client` class.
Returns
-------
~ipfsapi.Client
"""
# Create client instance
client = Client(host, port, base, chunk_size, **defaults)
# Query version number from daemon and validate it
assert_version(client.version()['Version'])
return client
class Client(ipfshttpclient.Client):
# Aliases for previous method names
key_gen = base.DeprecatedMethodProperty("key", "gen")
key_list = base.DeprecatedMethodProperty("key", "list")
key_rename = base.DeprecatedMethodProperty("key", "rename")
key_rm = base.DeprecatedMethodProperty("key", "rm")
block_get = base.DeprecatedMethodProperty("block", "get")
block_put = base.DeprecatedMethodProperty("block", "put")
block_stat = base.DeprecatedMethodProperty("block", "stat")
files_cp = base.DeprecatedMethodProperty("files", "cp")
files_ls = base.DeprecatedMethodProperty("files", "ls")
files_mkdir = base.DeprecatedMethodProperty("files", "mkdir")
files_stat = base.DeprecatedMethodProperty("files", "stat")
files_rm = base.DeprecatedMethodProperty("files", "rm")
files_read = base.DeprecatedMethodProperty("files", "read")
files_write = base.DeprecatedMethodProperty("files", "write")
files_mv = base.DeprecatedMethodProperty("files", "mv")
object_data = base.DeprecatedMethodProperty("object", "data")
object_get = base.DeprecatedMethodProperty("object", "get")
object_links = base.DeprecatedMethodProperty("object", "links")
object_new = base.DeprecatedMethodProperty("object", "new")
object_put = base.DeprecatedMethodProperty("object", "put")
object_stat = base.DeprecatedMethodProperty("object", "stat")
object_patch_add_link = base.DeprecatedMethodProperty("object", "patch", "add_link")
object_patch_append_data = base.DeprecatedMethodProperty("object", "patch", "append_data")
object_patch_rm_link = base.DeprecatedMethodProperty("object", "patch", "rm_link")
object_patch_set_data = base.DeprecatedMethodProperty("object", "patch", "set_data")
pin_add = base.DeprecatedMethodProperty("pin", "add")
pin_ls = base.DeprecatedMethodProperty("pin", "ls")
pin_rm = base.DeprecatedMethodProperty("pin", "rm")
pin_update = base.DeprecatedMethodProperty("pin", "update")
pin_verify = base.DeprecatedMethodProperty("pin", "verify")
refs = base.DeprecatedMethodProperty("unstable", "refs")
refs_local = base.DeprecatedMethodProperty("unstable", "refs", "local")
bootstrap_add = base.DeprecatedMethodProperty("bootstrap", "add")
bootstrap_list = base.DeprecatedMethodProperty("bootstrap", "list")
bootstrap_rm = base.DeprecatedMethodProperty("bootstrap", "rm")
bitswap_stat = base.DeprecatedMethodProperty("bitswap", "stat")
bitswap_wantlist = base.DeprecatedMethodProperty("bitswap", "wantlist")
dht_findpeer = base.DeprecatedMethodProperty("dht", "findpeer")
dht_findprovs = base.DeprecatedMethodProperty("dht", "findproves")
dht_get = base.DeprecatedMethodProperty("dht", "get")
dht_put = base.DeprecatedMethodProperty("dht", "put")
dht_query = base.DeprecatedMethodProperty("dht", "query")
pubsub_ls = base.DeprecatedMethodProperty("pubsub", "ls")
pubsub_peers = base.DeprecatedMethodProperty("pubsub", "peers")
pubsub_pub = base.DeprecatedMethodProperty("pubsub", "publish")
pubsub_sub = base.DeprecatedMethodProperty("pubsub", "subscribe")
swarm_addrs = base.DeprecatedMethodProperty("swarm", "addrs")
swarm_connect = base.DeprecatedMethodProperty("swarm", "connect")
swarm_disconnect = base.DeprecatedMethodProperty("swarm", "disconnect")
swarm_peers = base.DeprecatedMethodProperty("swarm", "peers")
swarm_filters_add = base.DeprecatedMethodProperty("swarm", "filters", "add")
swarm_filters_rm = base.DeprecatedMethodProperty("swarm", "filters", "rm")
name_publish = base.DeprecatedMethodProperty("name", "publish")
name_resolve = base.DeprecatedMethodProperty("name", "resolve")
repo_gc = base.DeprecatedMethodProperty("repo", "gc")
repo_stat = base.DeprecatedMethodProperty("repo", "stat")
config = base.DeprecatedMethodProperty("config", "set")
config_show = base.DeprecatedMethodProperty("config", "get")
config_replace = base.DeprecatedMethodProperty("config", "replace")
log_level = base.DeprecatedMethodProperty("unstable", "log", "level")
log_ls = base.DeprecatedMethodProperty("unstable", "log", "ls")
log_tail = base.DeprecatedMethodProperty("unstable", "log", "tail")
shutdown = base.DeprecatedMethodProperty("stop")
def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT, base=DEFAULT_BASE,
chunk_size=4096, **defaults):
# Assemble and parse the URL these parameters are supposed to represent
if not re.match('^https?://', host.lower()):
host = 'http://' + host
url = urllib.parse.urlsplit('%s:%s/%s' % (host, port, base))
# Detect whether `host` is a (DNS) hostname or an IP address
host_type = "dns"
try:
host_type = "ip{0}".format(netaddr.IPAddress(url.hostname).version)
except netaddr.AddrFormatError:
pass
addr = "/{0}/{1}/tcp/{2}/{3}".format(host_type, url.hostname, url.port, url.scheme)
super(Client, self).__init__(addr, base, chunk_size, timeout=None, **defaults)
def __getattribute__(self, name):
value = super(Client, self).__getattribute__(name)
if inspect.ismethod(value):
@functools.wraps(value)
def wrapper(*args, **kwargs):
# Rewrite changed named parameter names
if "multihash" in kwargs:
kwargs["cid"] = kwargs.pop("multihash")
if "multihashes" in kwargs:
kwargs["cids"] = kwargs.pop("multihashes")
try:
return value(*args, **kwargs)
# Partial error responses used to incorrectly just return
# the parts that were successfully received followed by the
# (undetected) error frame
except exceptions.PartialErrorResponse as error:
return error.partial + [{"Type": "error", "Message": str(error)}]
return wrapper
return value
def add(self, files, recursive=False, pattern='**', *args, **kwargs):
# Signature changed to: add(self, *files, recursive=False, pattern='**', **kwargs)
if not isinstance(files, (list, tuple)):
files = (files,)
return super(Client, self).add(*files, recursive=recursive, pattern=pattern, **kwargs)
# Dropped API methods
def bitswap_unwant(self, key, **kwargs):
"""Deprecated method: Do not use anymore"""
warnings.warn(
"IPFS API function “bitswap_unwant” support has been dropped "
"from go-ipfs", FutureWarning
)
args = (key,)
return self._client.request('/bitswap/unwant', args, **kwargs)
def file_ls(self, multihash, **kwargs):
"""Deprecated method: Replace usages with the similar “client.ls”"""
warnings.warn(
"IPFS API function “file_ls” support is highly deprecated and will "
"be removed soon from go-ipfs, use plain “ls” instead", FutureWarning
)
args = (multihash,)
return self._client.request('/file/ls', args, decoder='json', **kwargs)
# Dropped utility methods
def add_pyobj(self, py_obj, **kwargs):
"""Adds a picklable Python object as a file to IPFS.
.. deprecated:: 0.4.2
The ``*_pyobj`` APIs allow for arbitrary code execution if abused.
Either switch to :meth:`~ipfsapi.Client.add_json` or use
``client.add_bytes(pickle.dumps(py_obj))`` instead.
Please see :meth:`~ipfsapi.Client.get_pyobj` for the
**security risks** of using these methods!
.. code-block:: python
>>> c.add_pyobj([0, 1.0, 2j, '3', 4e5])
'QmWgXZSUTNNDD8LdkdJ8UXSn55KfFnNvTP1r7SyaQd74Ji'
Parameters
----------
py_obj : object
A picklable Python object
Returns
-------
str : Hash of the added IPFS object
"""
warnings.warn("Using `*_pyobj` on untrusted data is a security risk",
DeprecationWarning)
return self.add_bytes(encoding.Pickle().encode(py_obj), **kwargs)
def get_pyobj(self, multihash, **kwargs):
"""Loads a pickled Python object from IPFS.
.. deprecated:: 0.4.2
The ``*_pyobj`` APIs allow for arbitrary code execution if abused.
Either switch to :meth:`~ipfsapi.Client.get_json` or use
``pickle.loads(client.cat(multihash))`` instead.
.. caution::
The pickle module is not intended to be secure against erroneous or
maliciously constructed data. Never unpickle data received from an
untrusted or unauthenticated source.
Please **read**
`this article `_ to
understand the security risks of using this method!
.. code-block:: python
>>> c.get_pyobj('QmWgXZSUTNNDD8LdkdJ8UXSn55KfFnNvTP1r7SyaQd74Ji')
[0, 1.0, 2j, '3', 400000.0]
Parameters
----------
multihash : str
Multihash of the IPFS object to load
Returns
-------
object : Deserialized IPFS Python object
"""
warnings.warn("Using `*_pyobj` on untrusted data is a security risk",
DeprecationWarning)
return encoding.Pickle().parse(self.cat(multihash, **kwargs))
PK ENZ ipfsapi/client/base.py# -*- coding: utf-8 -*-
from __future__ import absolute_import
import warnings
from . import DEFAULT_HOST, DEFAULT_PORT, DEFAULT_BASE
class DeprecatedMethodProperty(object):
def __init__(self, *path, **kwargs):
#PY2: No support for kw-only parameters after glob parameters
prefix = kwargs.pop("prefix", [])
strip = kwargs.pop("strip", 0)
assert not kwargs
self.props = path
self.path = tuple(prefix) + (path[:-strip] if strip > 0 else tuple(path))
self.warned = False
self.__help__ = "Deprecated method: Please use “client.{0}” instead".format(
".".join(self.path)
)
def __get__(self, obj, type=None):
if not self.warned:
message = "IPFS API function “{0}” has been renamed to “{1}”".format(
"_".join(self.path), ".".join(self.path)
)
warnings.warn(message, FutureWarning)
self.warned = True
for name in self.props:
print(name, obj)
obj = getattr(obj, name)
return obj
PK Nr$IGST T ipfsapi-0.4.4.dist-info/LICENSEThe MIT License (MIT)
Copyright (c) 2015 Andrew Stocker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
PK !HMuS a ipfsapi-0.4.4.dist-info/WHEELHM
K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UD" PK !HϚ8 ipfsapi-0.4.4.dist-info/METADATAioJE?ͮ6"@rhhۍ1Jk/٪nLfv":*ZLP
߱{QxBFIkӀ/q{zk3^4YJ\u{ҹ"P߳.1uXqjv܌W:>u)H,䀥u'هvV(qp%@8g%3?UR59!`46tP?!ZR/a\W;!NoƎQ>Kٳ=wZq;b$WP IiyMhjUr<^#=AE %::{(JꀔlG3
GQP)>aGp=[]M+~{.X2!`6녌%^~۹3.ώ cE
ܻ"8N9)mtMh *ҤQMʿz8E_=>q5p2wm?bp(lm7v18e-5 >aoM0N!n2!7Ku
곰︳p:]Ф4&)?6Jm|h{~knګιD675f дߞlIˢ(n)3;+|*t>Kih2^ b]9LkyWr(,e0rhk4%sAC&0@}'2@"28MO
#?N=snf$K[5UpQ~{LT~TUjLdr nGVͩp8nwn^U+n} !9Nl;a4ǫ 8nj3s!1q I}FtΈ1@$a!$fHaxqc`pp^dRIJP*A>@ZkPX%d 6/E;A:>y9c) H px:wGdT٭Wj8\::!fD Z[%D"ϗ\\f1@SKȃ FVH ȕXC
N/J'VR R:pSPCm*IZ[U
\ttz+-S
l~5BW. 0dȮ@ Df奤xΠ8IY :,@ry/$/d^\uH4"+;)H2<÷KzY
=_)/Di\(M|;ļ.saGBKkPg6V.P~!J%Х.e(AWڧO
.ayCܶVT>"s(N(#S1 n!~/\R>.M0Xi^}қ7s2>3=TorG5IaR
#ԟ8|JG:d$Wq~(
mHJss9@ g`
;6@y${#Nj5ހ5G~Y^JWy̓@\kYGs#/*v᪦tLDdGwP;U+^^-\AvImU*SPTJ<0SٞEϷ+pjHb~k[{s8]]R^7Z˃l֭BFVFWJ
!tl0MMi|JwVOsooП7z`9G&ksOԑ(Q 0{LD
Q(H}<ј.it2ogVj/ 8
+=:n*~|/d
)%/g{8}XL{|Poҵny}wqӃNikmښ&h/Q4;ǽpQ3ςyGtʾyi?I9Urwy"=М,fc~{d08sNqg䎿D;9cATo%'Gl|ܟ?.E˦
851u#{M;7~UWtoR<('[1ި{lYu6{qN+;Nq{iGV\k >xɘX@{q\Y&WNSiB#En-Gۇ5kɢv0;Ktxu+w)lbrP9M{IPWIq#LU$ ::Wa~w Z[FӸÛC_j Y.oiFG;,54C{ }/dy.Y2#{Ep+x9>0|,Ÿ*"w)seUlۣz<\XإQ@̾ӷd+V*JՓr) ޏB|hnɓ66ᗙFA,kU=CSv]YY"a^@ f!5=hdۂի^MuO!O`tغ bK5lE: ʑ+ē0 %N 4GR [O'~wWgEPLu{n
2+^Q֪)fue2\^`AjƐ/jyǕA^dFŠhT
)S
#Pp*<~
K=KJG2kI-W\Q)M&x\$VZ]xn{6#ope nihnuMcoƁ41ct-
8,4mK"GZǃ6RaVMV&>/ bs6jd# 6 =OCt.O&g'tCBI9O>曣\\4)K1 rv=@6TjgB}0w|0UTiDmy1n=
*8Xc=cNsotE)}J<--.>E-^A&92Pr^i,!#,ư2/<}3b%