* API validates and accepts requests and writes to the DB * tests check all API responsespull/3/head
@ -0,0 +1,73 @@ | |||||
<?php | |||||
namespace Telegraph; | |||||
class HTTP { | |||||
public static function get($url) { | |||||
$ch = curl_init($url); | |||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |||||
curl_setopt($ch, CURLOPT_HEADER, true); | |||||
$response = curl_exec($ch); | |||||
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); | |||||
return array( | |||||
'status' => curl_getinfo($ch, CURLINFO_HTTP_CODE), | |||||
'headers' => self::_parse_headers(trim(substr($response, 0, $header_size))), | |||||
'body' => substr($response, $header_size) | |||||
); | |||||
} | |||||
public static function post($url, $body, $headers=array()) { | |||||
$ch = curl_init($url); | |||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |||||
curl_setopt($ch, CURLOPT_POST, true); | |||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body); | |||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); | |||||
curl_setopt($ch, CURLOPT_HEADER, true); | |||||
if (self::$_proxy) curl_setopt($ch, CURLOPT_PROXY, self::$_proxy); | |||||
$response = curl_exec($ch); | |||||
self::_debug($response); | |||||
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); | |||||
return array( | |||||
'status' => curl_getinfo($ch, CURLINFO_HTTP_CODE), | |||||
'headers' => self::_parse_headers(trim(substr($response, 0, $header_size))), | |||||
'body' => substr($response, $header_size) | |||||
); | |||||
} | |||||
public static function head($url) { | |||||
$ch = curl_init($url); | |||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |||||
curl_setopt($ch, CURLOPT_HEADER, true); | |||||
curl_setopt($ch, CURLOPT_NOBODY, true); | |||||
if (self::$_proxy) curl_setopt($ch, CURLOPT_PROXY, self::$_proxy); | |||||
$response = curl_exec($ch); | |||||
return array( | |||||
'status' => curl_getinfo($ch, CURLINFO_HTTP_CODE), | |||||
'headers' => self::_parse_headers(trim($response)), | |||||
); | |||||
} | |||||
public static function parse_headers($headers) { | |||||
$retVal = array(); | |||||
$fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $headers)); | |||||
foreach($fields as $field) { | |||||
if(preg_match('/([^:]+): (.+)/m', $field, $match)) { | |||||
$match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) { | |||||
return strtoupper($m[0]); | |||||
}, strtolower(trim($match[1]))); | |||||
// If there's already a value set for the header name being returned, turn it into an array and add the new value | |||||
$match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) { | |||||
return strtoupper($m[0]); | |||||
}, strtolower(trim($match[1]))); | |||||
if(isset($retVal[$match[1]])) { | |||||
if(!is_array($retVal[$match[1]])) | |||||
$retVal[$match[1]] = array($retVal[$match[1]]); | |||||
$retVal[$match[1]][] = $match[2]; | |||||
} else { | |||||
$retVal[$match[1]] = trim($match[2]); | |||||
} | |||||
} | |||||
} | |||||
return $retVal; | |||||
} | |||||
} |
@ -0,0 +1,71 @@ | |||||
<?php | |||||
namespace Telegraph; | |||||
class HTTPTest { | |||||
public static function get($url) { | |||||
return self::_read_file($url); | |||||
} | |||||
public static function post($url, $body, $headers=array()) { | |||||
return self::_read_file($url); | |||||
} | |||||
public static function head($url) { | |||||
$response = self::_read_file($url); | |||||
return array( | |||||
'code' => $response['code'], | |||||
'headers' => $response['headers'] | |||||
); | |||||
} | |||||
private static function _read_file($url) { | |||||
$filename = dirname(__FILE__).'/../tests/data/'.preg_replace('/https?:\/\//', '', $url); | |||||
if(!file_exists($filename)) { | |||||
$filename = dirname(__FILE__).'/../tests/data/404.response.txt'; | |||||
} | |||||
$response = file_get_contents($filename); | |||||
$split = explode("\r\n\r\n", $response); | |||||
if(count($split) != 2) { | |||||
throw new \Exception("Invalid file contents in test data, check that newlines are CRLF: $url"); | |||||
} | |||||
list($headers, $body) = $split; | |||||
if(preg_match('/HTTP\/1\.1 (\d+)/', $headers, $match)) { | |||||
$code = $match[1]; | |||||
} | |||||
$headers = preg_replace('/HTTP\/1\.1 \d+ .+/', '', $headers); | |||||
return array( | |||||
'code' => $code, | |||||
'headers' => self::parse_headers($headers), | |||||
'body' => $body | |||||
); | |||||
} | |||||
public static function parse_headers($headers) { | |||||
$retVal = array(); | |||||
$fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $headers)); | |||||
foreach($fields as $field) { | |||||
if(preg_match('/([^:]+): (.+)/m', $field, $match)) { | |||||
$match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) { | |||||
return strtoupper($m[0]); | |||||
}, strtolower(trim($match[1]))); | |||||
// If there's already a value set for the header name being returned, turn it into an array and add the new value | |||||
$match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) { | |||||
return strtoupper($m[0]); | |||||
}, strtolower(trim($match[1]))); | |||||
if(isset($retVal[$match[1]])) { | |||||
if(!is_array($retVal[$match[1]])) | |||||
$retVal[$match[1]] = array($retVal[$match[1]]); | |||||
$retVal[$match[1]][] = $match[2]; | |||||
} else { | |||||
$retVal[$match[1]] = trim($match[2]); | |||||
} | |||||
} | |||||
} | |||||
return $retVal; | |||||
} | |||||
} |
@ -0,0 +1,13 @@ | |||||
<?xml version="1.0"?> | |||||
<phpunit | |||||
bootstrap="tests/bootstrap.php" | |||||
beStrictAboutTestsThatDoNotTestAnything="true"> | |||||
<php> | |||||
<env name="ENV" value="test"/> | |||||
</php> | |||||
<testsuites> | |||||
<testsuite name="comments"> | |||||
<directory suffix="Test.php">tests</directory> | |||||
</testsuite> | |||||
</testsuites> | |||||
</phpunit> |
@ -0,0 +1,142 @@ | |||||
<?php | |||||
use Symfony\Component\HttpFoundation\Request; | |||||
use Symfony\Component\HttpFoundation\Response; | |||||
class APITest extends PHPUnit_Framework_TestCase { | |||||
private $client; | |||||
public function setUp() { | |||||
$this->client = new API(); | |||||
$this->client->http = new Telegraph\HTTPTest(); | |||||
ORM::for_table('users')->raw_query('TRUNCATE users')->delete_many(); | |||||
ORM::for_table('roles')->raw_query('TRUNCATE roles')->delete_many(); | |||||
ORM::for_table('sites')->raw_query('TRUNCATE sites')->delete_many(); | |||||
ORM::for_table('webmentions')->raw_query('TRUNCATE webmentions')->delete_many(); | |||||
ORM::for_table('webmention_status')->raw_query('TRUNCATE webmention_status')->delete_many(); | |||||
} | |||||
private function webmention($params) { | |||||
$request = new Request($params); | |||||
$response = new Response(); | |||||
return $this->client->webmention($request, $response); | |||||
} | |||||
private function _createExampleAccount() { | |||||
$user = ORM::for_table('users')->create(); | |||||
$user->url = 'http://example.com'; | |||||
$user->save(); | |||||
$site = ORM::for_table('sites')->create(); | |||||
$site->name = 'Example'; | |||||
$site->created_by = $user->id(); | |||||
$site->save(); | |||||
$role = ORM::for_table('roles')->create(); | |||||
$role->site_id = $site->id(); | |||||
$role->user_id = $user->id(); | |||||
$role->role = 'owner'; | |||||
$role->token = 'a'; | |||||
$role->save(); | |||||
} | |||||
public function testAuthentication() { | |||||
$response = $this->webmention([]); | |||||
$this->assertEquals(401, $response->getStatusCode()); | |||||
$data = json_decode($response->getContent()); | |||||
$this->assertEquals('authentication_required', $data->error); | |||||
$this->_createExampleAccount(); | |||||
$response = $this->webmention(['token'=>'x','source'=>'http://source.example','target'=>'http://target.example']); | |||||
$this->assertEquals(401, $response->getStatusCode()); | |||||
$data = json_decode($response->getContent()); | |||||
$this->assertEquals('invalid_token', $data->error); | |||||
$response = $this->webmention(['token'=>'a']); | |||||
$this->assertEquals(400, $response->getStatusCode()); | |||||
$data = json_decode($response->getContent()); | |||||
$this->assertEquals('missing_parameters', $data->error); | |||||
} | |||||
public function testMissingParameters() { | |||||
$this->_createExampleAccount(); | |||||
$response = $this->webmention(['token'=>'a']); | |||||
$this->assertEquals(400, $response->getStatusCode()); | |||||
$data = json_decode($response->getContent()); | |||||
$this->assertEquals('missing_parameters', $data->error); | |||||
$response = $this->webmention(['token'=>'a','source'=>'foo']); | |||||
$this->assertEquals(400, $response->getStatusCode()); | |||||
$data = json_decode($response->getContent()); | |||||
$this->assertEquals('missing_parameters', $data->error); | |||||
$response = $this->webmention(['token'=>'a','target'=>'foo']); | |||||
$this->assertEquals(400, $response->getStatusCode()); | |||||
$data = json_decode($response->getContent()); | |||||
$this->assertEquals('missing_parameters', $data->error); | |||||
} | |||||
public function testInvalidURLs() { | |||||
$this->_createExampleAccount(); | |||||
$response = $this->webmention(['token'=>'a','source'=>'notaurl','target'=>'alsonotaurl']); | |||||
$this->assertEquals(400, $response->getStatusCode()); | |||||
$data = json_decode($response->getContent()); | |||||
$this->assertEquals('invalid_parameter', $data->error); | |||||
$response = $this->webmention(['token'=>'a','source'=>'http://source.example','target'=>'alsonotaurl']); | |||||
$this->assertEquals(400, $response->getStatusCode()); | |||||
$data = json_decode($response->getContent()); | |||||
$this->assertEquals('invalid_parameter', $data->error); | |||||
$response = $this->webmention(['token'=>'a','source'=>'notaurl','target'=>'http://target.example']); | |||||
$this->assertEquals(400, $response->getStatusCode()); | |||||
$data = json_decode($response->getContent()); | |||||
$this->assertEquals('invalid_parameter', $data->error); | |||||
} | |||||
public function testNoLinkToSource() { | |||||
$this->_createExampleAccount(); | |||||
$response = $this->webmention(['token'=>'a','source'=>'http://source.example.com/nolink','target'=>'http://target.example.com']); | |||||
$this->assertEquals(400, $response->getStatusCode()); | |||||
$data = json_decode($response->getContent()); | |||||
$this->assertEquals('no_link_found', $data->error); | |||||
$response = $this->webmention(['token'=>'a','source'=>'http://source.example.com/nothtml','target'=>'http://target.example.com']); | |||||
$this->assertEquals(400, $response->getStatusCode()); | |||||
$data = json_decode($response->getContent()); | |||||
$this->assertEquals('no_link_found', $data->error); | |||||
} | |||||
public function testHandlesMalformedHTMLWithLink() { | |||||
$this->_createExampleAccount(); | |||||
$response = $this->webmention(['token'=>'a','source'=>'http://source.example.com/invalidhtml','target'=>'http://target.example.com']); | |||||
$this->assertEquals(201, $response->getStatusCode()); | |||||
$data = json_decode($response->getContent()); | |||||
$this->assertEquals(false, property_exists($data, 'error')); | |||||
} | |||||
public function testQueuesWebmention() { | |||||
$this->_createExampleAccount(); | |||||
$response = $this->webmention(['token'=>'a','source'=>'http://source.example.com/basictest','target'=>'http://target.example.com']); | |||||
$this->assertEquals(201, $response->getStatusCode()); | |||||
$data = json_decode($response->getContent()); | |||||
$this->assertEquals(false, property_exists($data, 'error')); | |||||
$this->assertEquals('queued', $data->result); | |||||
$this->assertEquals(true, property_exists($data, 'status')); | |||||
preg_match('/\/webmention\/(.+)/', $data->status, $match); | |||||
$this->assertNotNull($match); | |||||
# Verify it queued the mention in the database | |||||
$d = ORM::for_table('webmentions')->where(['source' => 'http://source.example.com/basictest', 'target' => 'http://target.example.com'])->find_one(); | |||||
$this->assertNotNull($d); | |||||
$this->assertEquals($match[1], $d->token); | |||||
} | |||||
} |
@ -0,0 +1,2 @@ | |||||
<?php | |||||
require_once __DIR__ . '/../vendor/autoload.php'; |
@ -0,0 +1,14 @@ | |||||
HTTP/1.1 200 OK | |||||
Server: Apache | |||||
Date: Wed, 09 Dec 2015 03:29:14 GMT | |||||
Content-Type: text/html; charset=utf-8 | |||||
Connection: keep-alive | |||||
<html> | |||||
<head> | |||||
<title>Test</title> | |||||
</head> | |||||
<body class="h-entry"> | |||||
<p class="e-content">This page has a link to <a href="http://target.example.com">target.example.com</a>.</p> | |||||
</body> | |||||
</html> |
@ -0,0 +1,16 @@ | |||||
HTTP/1.1 200 OK | |||||
Server: Apache | |||||
Date: Wed, 09 Dec 2015 03:29:14 GMT | |||||
Content-Type: text/html; charset=utf-8 | |||||
Connection: keep-alive | |||||
<html> | |||||
<head> | |||||
<title>Test</title> | |||||
</head> | |||||
<body class="h-entry"> | |||||
<p class="e-content">This page has a link to <a href="http://target.example.com">target.example.com</a> but is broken HTML</p> | |||||
<p><div><a> | |||||
</body> | |||||
</body> | |||||
</html> |
@ -0,0 +1,14 @@ | |||||
HTTP/1.1 200 OK | |||||
Server: Apache | |||||
Date: Wed, 09 Dec 2015 03:29:14 GMT | |||||
Content-Type: text/html; charset=utf-8 | |||||
Connection: keep-alive | |||||
<html> | |||||
<head> | |||||
<title>Test</title> | |||||
</head> | |||||
<body class="h-entry"> | |||||
<p class="e-content">This page doesn't have a link to target.example.com</p> | |||||
</body> | |||||
</html> |
@ -0,0 +1,190 @@ | |||||
HTTP/1.1 200 OK | |||||
Server: Apache | |||||
Date: Wed, 09 Dec 2015 03:29:14 GMT | |||||
Content-Type: image/jpeg; charset=utf-8 | |||||
Connection: keep-alive | |||||
�����N�ri@���5}�yj�>��>(��v�T�7@� | |||||
� >�u� | |||||
�7_��S���yD��n;��26���Կ��ddu9�J8 4��P1n��O�]��h�26@�Kv<�$< | |||||
lz�OwQ}��?��"W79ٗ���7�QjN{�;��[S� | |||||
����>�qA��r4����3� �!+�%�� �z��� ��ΩJ��%nB�T�g�\#��sځ�w�L;��� <H($�:Y�� | |||||
�����59����ٶb"�TA�\ؘ��z���7֯�k� �1�H(>�gzR����܉�Sk8ȶ(;�;�Z��pAS��La�A���DG��@����b�p($�P������� �0���˜B8�,G�A�"Ƙ �Q�~��悃� | |||||
((���,s� u� ��ˎ���|sAr2��PHt�f� ��r2q� | |||||
3h��g��{��z�n��O�al&��y{����;����0(:?���-�6�"+��Dr�O�t�Y�� Oٖk�� | |||||
�tM��@�B�Jw�Y�>�'A��hؑ�o`�1V�@�������,�O�Pa��\�Y��Q�-�T���6�Չ>����D�7c�۰�Yt�gRa�w�r�� �ê 7�m����m�A���8�+�a�%/�fu�Xw��A����ei���������x4�y{��0�G��hu��@[j�;���dk8Pp�=��c-#he���5s����coapL��T��_������>x ��t���+�\���5��Լ�m(��1�z����N#-&���n>o��ѷƑ��\�\�[��;y���#�ܟ��Q�ay�\fY�|�Z����Sك=���(䂭@�B��t��<`� )T��iA� p��㍷�F@}�ke��K���{1��.;P|� ��8mbO9�=��tC��@|Q�)H&:$܀!roL��(Zۙr@ �큗����N2 �}��D1A��}���,��I���|� r�T�@�za�|3Ч.�t��� ���Z��_ӭ�����表O�(h�3��ô��a��/� ���@�zE�@��oW��N?]�ơσ ��k���t�Iĩ����0�r�nd䏂Fv�@j�<01Pkf�Z��j Un�0��A�zNv��ă���oh�V1�4C��A��d�}EpK�s�|� �{X\ | |||||
k3�c���H|x�Am��*�d��.��&[�-��2 7�F��?�@�t��$pA3���d� J8��i | |||||
)\�.C��A"�܂';��N҃H�7Q��somB�y�0!l��CenR�}��z����HH���u����j��S��������A'� | |||||
a��.>�$�|8�x��$���}�4�H@���7�O��Ak� | |||||
Tw7L^��\�dn�9�>���Ku�ȄZ��G�~(7�>�� 鱹��pk-ܭ���P�ߔ�ά���W�|J��G��C��MF#��O��3��QTpn���FnPX���|�>(4��vE��5A�뛁Q+�dĠ��J1�7bND��^��c��k�����L��$�J | |||||
����^� | |||||
zd�D���*=iW���({��D�/H����Y����D�b�zzu�!�&+�kՌ@�KРq�U�~���@K�]HO��~�O� ��^��ܝؙ�pA�}��E�N2���2����pz�%��NL�2��'���R���D�W�4j���}J3""%Ww��s��N� | |||||
��D~�}��Z1M��PM�{�K�� | |||||
Db�x����$��"���t�r�h� �"#��#w/��=�&=o�DJ�)�6<�~�əʄ�A��� | |||||
��ڃ}�� | |||||
ё�,^�>�g��h��N��� ?��RJ2#�`�B�'��,@A�k�]�kD@%�Y���w�{S�H�p�0�4�ز>��&/9r�CS� q�Q�cIJ_4�19��;��Mˑ��t-�"@�4��H | |||||
{�E�µ$�n�kgubfA9P:�(�\� _�!��%�"���V���X�O�b | |||||
=R���li� | |||||
;�P�n�YDsq��v�� | |||||
�Q�)NP�R9{�=cNU/@#ԧ&x��]��[�wP �]B�Ţr@��7�&$�}f�G���]�����[�� ��� H��d | |||||
�(���R1�!�P��b@�4����p=$���@��A���u?� | |||||
���u��a�ʀ %�n�ck���2&:���Yw:��qhً���)�����}p�AG� ��c�`x6H2��-ă�c��;ִ | |||||
�0��.7��7;ܬ3 V���n((��0���o�b��?)�@���gxA��㉊����]R1���]z��+��r�U,�$� `����kW�H�s�Ɔ@��,d�|PtB�Z��meL�T�A�pAv�DT�����@�K|���6�PJ���=f | |||||
S7(0��,ʗ!8�����KPA�����5}1c�W�#�d�� | |||||
��EƐ�qA���+�Ǩ�VǛ� �R�%��z[��b�2zd��C�A��l]��(&wvN�ף!������Ư | |||||
n�: m�����P-> | |||||
R���V��A�osK� | |||||
{ ���`J�ٱ��S������E�;� ���)_۷��}F�_�?�n^�~w�A��~Fdw0>k�Dw��(;PW�Y�D | |||||
n�����N�hKz�bw9ffoۑm]́��A?mh B0���%�?�A���N�*ږ�� ��~�ļ��*�A��V�Yݦ�7�Ӷ�58惿o��N��x=Oi+R"�pC �\��� | |||||
�Q�b�9�D=1@��Z��}'D�Y� �-��b@(;� ʡ�h�pA���MY۵WA�,3@��ʃ<L���0�h�6����b��J RXbt�#���i�!�1�"( � S>(/�P�"��F2�%���w��D�L|B�T�s@�Ȝ+� | |||||
Z'�� | |||||
�k��J�b�Q��L��ڂ�Ng��PG��M�L@v���}:�rIǧܙ����AP�]v}D �ӦڮT ����N��5A���@N�%��E����Kp@���3zd���fHx����\��M�giw���oq��6nm&\B���Zٮ | |||||
L���`;#jR�m�l� ������9��t | |||||
;S�� ����@�PdvgH2"��Xlaƕ� | |||||
�+��$���W �#�I���$a��A� (�� �N��cY�C�"�zD���6LB�1�&?�c���|D���a-��Tnn #�q�1�A��g#"uj�A�gG@����z �� | |||||
��p$ �Йm�v�.; | |||||
�H�:�pA���bG�9�D\�=)��� �{���yz��x R7�&�r�e=�fb#! X�� | |||||
�{4��pP�6$���A�f�� | |||||
wH�{��G�@�v�%,�J�"�>N�9� @��F W7�5D����ȁ��� | |||||
��s�PW��?�i��w&�qۊ | |||||
��s:¨/U��~��"����g�1���5�\��2a����Cѻ�De}��H.3�Y��P9}C�#��v:�"�9��a�:K�#RHA�'a�?y`� oF_�A~����\��c�I2/ƨd�A1������A�ol�� | |||||
�-�0A�;d�aj{�L�mۯO�z������$ �fA��܁���ڂ�&�L� | |||||
n/���Qz���3�����;�m�yd;h�9 | |||||
�C�@���zv�"³�PK�@$�Q; A&P��.� ���;X ���+�8 �[3 | |||||
�#K ���F5��#zmP狠���!��3���3$�zH�4A�f�=�i:�'(�S�+�F�A��HK�J�9�#֖Qo� �(z��v��9�%�)�bf[0� | |||||
�jJ�$���p!귞�\ | |||||
�iM��jM�I����-�V�S��M�ԫKqv{�V�ݾ$$j�/ڃ���/�kD�R��$^2,sA0�A�t+�]c�����Pd�1!� �ɨ1����,x F3��<�7�,C���p� | |||||
�A0j R0 | |||||
A�spt��64(w�.��P8�0��(+L�l&�9�kB+��lK<~�8g���ZTb��bp�@L�P��dt�@W0���OR���%� �<;|%�čc��t����� | |||||
DX�Ap�"Ƨ�A�oY�29�M�I:�V��(�$�;Pif�y���M�`�{C���s�R�U�&v=������� | |||||
��hĜ�tGwq�E��Ǩ�ɥh�dY;�u�_��Pă��Χ�#� � | |||||
��q��� ٖA�.=2E� | |||||
�A�H��z�I�=h���dQ���<���&�q�g�8�`w[Q���UX���y��Th�-B1ƀ��wF�h� �f�g;�1�.��[�p2���5�(�Ug+�I��k�H/ִ�ķ�|P)JƒM�[��wS��P�9pA��3/B�HH>(*3 � ��q��&�;�^���,���)4��+lH��-���f���ޝ�܂�a�3�p,�~�5rx1@�mȓ�#G���v�A���SY� | |||||
K� | |||||
�-̇/.�J\2��S�� | |||||
�GxA���a@Z��.�pA���c�hm` ��9� | |||||
G����$�e��6�s`��f%�K�j�� | |||||
�'���2�����:mY�#����/��&H��� | |||||
W | |||||
i-J _Kn �9=�L�-��A3�)�H�ga(�`�.[��q@�<�: | |||||
4�Dw��Z�*�,�"�c�\�"�::�v(��q�"�@�(&�s�A��KMo82����*a��O0@� ��$ г1� ����W�7�)Āp�Py&��A� | |||||
�&~�Y�T#0k���K���U>�S� �dməM��Q�G� �����.�0��G��T.a�d���zn�Y�Ĕ | |||||
[/���G��0�j� | |||||
q8����� | |||||
�@ď� S� | |||||
��P�=��&���AҀf�}5�P�J�@��@�� | |||||
ҙ�����RY3���[�Ӧ!�L�Af�Xi%�F�g�N��X�}/� �]�� G�\ ����8 �ҘƝ�,@������9BD~(0�c�b�[ �����z��,�-{��v S�� | |||||
���,��V��Ncl��1c�� ��z | |||||
k}� | |||||
^r@3�~�w���jF=��^q��� �oV�@�Jo�ڃ0�\Ҍ�X3}��8C��"ゃ���^���L39�����"9�[m(��/X#��@��$68q�A�G( | |||||
�D�M��AR9\��H9�'�q�HvaO�:�G��k�@�0#C^ 38 � I���8�X�r?� | |||||
�����ڝ˧� ���n�����>j��b����.۷!�H<�bPo Ͱ=�5��ı�(&7�F��� ~����A_VH��{�>~�73-��AF�#�F�%������{ @�14�A��^��� PA�W�'��1���aހ���ي | |||||
�ԦY۱�U�������V�C^�/D�rst:��rX܂��\�N��է�(*=f9����m�1$���X�Hz���L5�A���GJ | |||||
�X�����]��������#� ���ǞZ��A���ߙ�qt�ć4 �h� ���� ��b3� | |||||
w��s��\�y��5("P�.�6���;Ї,�!a�bB��] | |||||
E�Rd��J�?jLF� �I����c-CO`!� | |||||
����}P)ډD���D6V�3�z | |||||
c��8��6A�oi�C����F�AR��i\��[M�f� ��j���=2$��#�}0� �ېIq���;G)v@���/� | |||||
��$3�D� (,ܴ�e�b | |||||
��CT�e�h)�� | |||||
�"(��T���� | |||||
Ɍ)��^�cCHi�$ ��2��g>[:C�A����&A��p��A�tV���`��.2'� ����5s��w]u�N@�( | |||||
�]�1��"(o�F@�~aT>#���"E��/��� R4}H$ܘ�T<PH����.���T%1C" ���s=�9�D~�� | |||||
�� Z 3�A7%^R@�vΐ�!z�� | |||||
,�Bo��F����h����{��c'ʈ4��C�z�U� | |||||
!+�(2���w�@���@��%7 ���X�� | |||||
��H���D����ȇ�X%FmS'�A�kr���A��M��p��N�P�|J | |||||
��96��'���@j��($�Q�'�S�A�� | |||||
x�--F@�'g/A�����>i���r | |||||
B9G�A��cF�$��@=���X���l��D���ft}t��F( ��/�������4�w���u#'a�s�n�&o� �3-OWtv�N��As�;Aշ��,��'9����T��/�����ȌK�����\0��� | |||||
-��: R$�%G��)O6C��ct�1�A��J1���A�u���x W'&� 2 | |||||
� | |||||
��õ:�c��WV�I#�:�B?��D9Ձ�KP�9�@7!���@D/�@k��ڀ��qA������<��p9�lܹ�Q��c�[16�� | |||||
�D� ��J�,Ny2���-��I��l��}'A���b�COw��vE%h�N=�7=l�}� bx��tM���F'�0�k~���ܟ!Tsۛ\���(.״�R��Dx��]��l9*�G"{p]�M7D-_��Y2fێ�#���R���`�#�貺c=�G�!�Js�~�kf�A~v9b����zD�nX�3Mq4�A��c�n6u | |||||
�A(1����d7�:���Pko�c��1�zq�05?�D��}����,3�}r��;�Nۋs�8�c��?ԭ��E�Ē�����Csb�Ř����m�`g�Pg�@�qG�SA��L��2t��m|��o,ȳ���Es��uX �;_��(9�����\ | |||||
��{� ��$�b�����Y� | |||||
2�7n>��@�W� | |||||
O��� D���(�fu��cfձ+�1��>lx �������,�M���f�1(2>���ٻڈs6��q���]���KS�a� �.GsnM(�A���7d>Z�� | |||||
�|�%�d���}����H���s�u8Z�vv�-]ӑͨP�}T�gQ�v�(�ޡR-ȶ8����FU��AP��jЙ`P�P'�7�A"0���k�� �����D��$~(g��"��m��v�3�@�Q1��3ԱA���cjT�( �t�aDF���A@n��]Ж�D��0�V�[t�1��}MԜ��c��wP�1�@�pFlPTon���� | |||||
"I�ACs��@1��@������Gs|b�Tw{�������{B ��j悆������ϖ�ڀߘ��{x�ǹ�P�>b�Q�wY���R�5n�ȡ'�@��^���nF2,�3��H��'K���Fh$�dՑn��܁�����١(/X�7�zH g|3�<H����lx��쓂��E�x �n�x�-� �W��e�P�]�� | |||||
Ԩ�P�]j�@��p��P1�ܜr�p���n.��+�/H� ����$C �o�%��X�((��s��3��vq�a�w[��3\PL�WdR*;��C�}n��";�/��?��h��lJ�n#z�8��. @�[乑��t�Sw: | |||||
��A���2@�_�ځ5���P�� | |||||
!��4JE�A��� M����A�@�E((X��R2!�7�c?�x��;{�l]�(�_�ci�1ՠ�6ʁ�-�ʀ;M��b�CmzRa=���93 % | |||||
��Ÿf���KL���Pz3�D=~���4pF<Ph�N�Q4T���&A�1�E�Ć�PD%L��p@@�� | |||||
PL�$qn�꘍M���P��� | |||||
�$7w� ��us��XA�\8��1� � | |||||
p'Q��*2� �ÂND�IĴMy��)_�i��h� | |||||
�o�ĘNs|^ս��:�Ӗq-� r���ڕ�\�k��x �{]���A����A�Y���].P�GO�X�"G/c�vC*pz�[����Bْ��b | |||||
i(�P | |||||
=c�acҶ�4�� ��/����i��R{({{h+��d�g���A_�?��l���J�>�۵&�"\ �]P�,��(3't!�5� | |||||
a��m�ۑ�K5~(0����И���u(�� �K�\jyc\� �=d<��� | |||||
Pv��MT6-�I���v��@&�86� | |||||
�(�41�6=�er J!�X�(�d/�����r�(3�ۑ��vE�N8qA�����H�-MQ��I�j���S܂�w}��2��������ݷn��KKgŻ�|GS�E�-Ɲ�<������I��X�P}���/m�oq�#P<��)�`i�����;77��ں����(7k�ދ��H��q��<��Ԡo��x�:P��glPc+w3bs��W�c(�A������zwA�8�?�(̃�m.���2.u�4�f�dn���)D�� ���z�7�32�jj | |||||
aD | |||||
P� 5KP$Ȓ$�j�3��+R �X?aA;=��7��uڐ�"X��t����\1��JF�0r^����rW&R�d{ ����k� | |||||
r�8i�i� | |||||
!���Of�mk�ļ�bǀA�X Õ�;{}ƻS�OLPD=�:���F��ŀ�A�{ejw<���GdZ��+f���[���F�m��4GLd�sA���=(�������Dڶ"�*�E��q�`���G�"љ1"�qI�P]��j�^q-�(����X��z��D�nZ���yc��[��#�l�Om | |||||
b��uA� ����bG@�����6a�O�?���@ [��eͶ��܋H�Q� �[(��A���XĸA�v�'�3A'g�x �m@ | |||||
�H���N(7�F�0��� �>�-=��� ��mj�ǘl� 3�Z Dh�DͿR:I | |||||
�t=��R��d�7;i�N.�ͻft���]��8�;Cl9-�P`-��~�[V�P�ېHx�s�F�x���ȥ��@�[��H��F��� 舿��T��e!Lç\2�� ���sCKdB | |||||
�������a�O���ߏ-؏D�F<Jrř����A���" | |||||
��$�����%��3�K� i=��BE0A#c's!�J�+�k�J$��r | |||||
�H�|�� ��;�8�����mfħ��O�����<õ1�C_���B&Fn{Pcpى���fe��;L��z��66�7z | |||||
�ݍA��Z,� ���=#*��V"&�8��V-ѣ��;E��'A�>�r�"�PLzm� | |||||
�(5���4�v@ٻk��"N_�A0�r��h3�M | |||||
��Pz}pbsA�;;�Q�0A�ڻ.)���n-� � | |||||
9�A�㽂 | |||||
�ǯ�����w�Je | |||||
1h�A�����.Q@58.Fm����k�H3�+�S��1�)��.� Z���|Pw9�H�� | |||||
c� sp8`PP2���x��^-S�sȠ"��S0��D�J | |||||
�����'"�0�(��x �{��{y�۱I6rA���pA�\�(Z������ | |||||
�C�� Gkv�Y��j4�J | |||||
Q�.%�J�j<IA���?Q��r�Q�WN�g�����昏4�H:�o��E�r�ʈ.;�_�#h���A�ڝ��K�"B�wA�}�'I�6��P9��"�!3�ƣ�P8�m�e^҃?��'p | |||||
�)|�TqA���\�7bF@O��Z$�Ps�u��hi�z����������O���72yJ�1��A���/NQ�A�7FRAcG�A��]��Uw�x�1#�$ݽ #��{�zS�� ��n��NT���"9E6Aw%�i�xH�27�N��&$��^���f5`�Kc�;i���;�-�� | |||||
��{Xm�&�I1r | |||||
�[�������� /�~��,J��{12� | |||||
՞[�bA4|��\J-�cq�'ՁA�z��c\f"I��8�A�suc�.���@�T | |||||
���=����&r �Dw���j�&D��=[Q���&[��L$pA77��,d@�8z�f05�@�7{��@A���"��籐g-���bx�ꎸF2�u~�8� ���[&��sw��J{q-D�Yb"4��f$"u����wej j%��A��' =I;��f�T�|�a{H | |||||
m�n#&2�mPL���� ����,�v��r�(��N���B��X��ר�H��s8���4���Kw | |||||
��Q�d�~�+��.NM�B;K�:E�ӈ�@��V��E����2~�ڃQ��1�샠n� | |||||
�sp�!���;��V܁��L��Ȑ�-��r���є"�j=�]�s��|� ɳ | |||||
u�97}?p$��z~��Oh��{c(@($ٴC��q����2|c� O�w | |||||
T6��oA� ��n@�� �����A��zTg2~�A��CN��e-���Pi�q���PX�m��w�D-��P8���ݵ#���������7�+3bzV��:"�9.m-�E�P`:tA��%� ��� | |||||
����� �@����;[x;���q�˒@w ���b^bG�Av� D���o 4� | |||||
�r#M��Ӕ | |||||
C | |||||
:Jv�<����ރt0� Q1�Aq�肧 ��9� Ip݁I�1%�AP�C���a=�������Ł0 �A7l�b9����27� | |||||
Z�qAV,ݜN�{���з"q'�q�k�gs�$8 q�n�,>�F� ���8��MG~�bm.{�/[O�x �^V("{�8B�\ �n k(7oY���T�&�nep٤I/�e��"�n���^��6���u��;����c�A�7���M���v��R����>?� �j5���n� | |||||
8�-��2�n�9���q�'�� | |||||
����� �/����Ysdx���8�PN�E<��@�H�x� | |||||
��� z"�#�$�c��I�?)�}:��@�$+�����C�:~�rH��cĚA���q;��Je�"��!�0��sOqr��e2kJ���w���}v�D�L�%�boߑ`O�@k�Ϭ�����$�B��-ނ}k��n�;� | |||||
>�A�����tР����M{PU��v�h0�Ǩݙ}E� | |||||
-{�{<�J=��臺�p�1�K�ǻ� �� ���o��J=��DLD�A����ᆪ`7�[��g����eL�Z�F��ܓ�t�tnd�"�2Ps�7���� | |||||
1<*�h{��=��O�>�ꑤ_��7���8�@j=�4��?W!�F���L��� G�m�/Q@:b2w�es��2&<0�i�.�!�r�1�@����}t��� ��q���c�đT}�$�Ͻ'�[���G^����Ks3)H5�6��,���(0����90��$&�1� Ԇr | |||||
��T��H�[�A��qz��[a� | |||||
r"�B|M��&ߤu�s�{ �vf�f$$C�N�f:��PT���H�A��Xj �Z�Ψ�9t�M���0f=����P+�B1�@/�ӱ��-_.MD}M� | |||||
y3�*�gzq�i�^v� | |||||
�M��J�`� | |||||
���Q�� | |||||
Z(+�3r�V��2���9R��r=^@yj | |||||
M��tsu!o�@�E× ��o����KzU��x pݽ�# | |||||
�n�b����<";�m9]������ո����q�D�p@A��o@��,Pa.��v�j� | |||||
��7 �As�X,�qJ�n��U�,�oFd | |||||
Mb | |||||
�j;���#� �7f(9�۴-j:D���A�^��$� | |||||
�2�E0d���kӳ���/�el� �<� �A�f�-�=o | |||||
c���A_^W $���I��: ����F�Ҁ��v��x��j�f | |||||
��lL����d2�u�m(��2[�n��P��1����ߊ��5��FL"���J�`Hɨ�����wr5" �]>k��A��H��|�hv�[���@ | |||||
�/G�rd | |||||
� | |||||
ڃH�f(t9� �/)��D��cC.u(���PT���h2�P�,$���Y�n�O�Yni[�PTw0�%� | |||||
���\�{�GDZ5A���ټ�-�Kq���I@ | |||||
X;�s�yƭe���P!r��"8��=%�2ܗ�@10�/5��/ | |||||
a����@CC��^�2����@�pDRd7j �: | |||||
V���l �߈�8��n6��!�wl7�";�Sl����k���ۉ�V��z�qA����9A��� � ꉗ�:��jr$ | |||||
���a� | |||||
��uX2��ۃ���B583�j�x����nP9��˖��(�dd�e�^����z���0�Xeނt�yk�zvΡ�2�3�a(�e/�#�(%��r�<P����0���X�&�~�Pԡñ�K=xIi� | |||||
� | |||||
]�W�p#��D�(�� ~���A��\̓�N7�� �@���D� | |||||
8@��2(/O������(���:FP2-��Pg��9��+|*��2� | |||||
��Z8���h'҃:-D��[�zLx�5�c0��$� ��ӄ� �T�ڐ?�,F��~�j�L�7�c\�8]���@z��� |