aboutsummaryrefslogtreecommitdiffstats
path: root/phpBB/phpbb/cache/driver/file.php
blob: 1e9ee960dccbb3e0e9d3140db35935ecc7a67324 (plain)
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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/

namespace phpbb\cache\driver;

/**
* ACM File Based Caching
*/
class file extends \phpbb\cache\driver\base
{
	var $var_expires = array();

	/**
	* Set cache path
	*
	* @param string $cache_dir Define the path to the cache directory (default: $phpbb_root_path . 'cache/')
	*/
	function __construct($cache_dir = null)
	{
		global $phpbb_root_path;
		$this->cache_dir = !is_null($cache_dir) ? $cache_dir : $phpbb_root_path . 'cache/';
	}

	/**
	* {@inheritDoc}
	*/
	function load()
	{
		return $this->_read('data_global');
	}

	/**
	* {@inheritDoc}
	*/
	function unload()
	{
		parent::unload();
		unset($this->var_expires);
		$this->var_expires = array();
	}

	/**
	* {@inheritDoc}
	*/
	function save()
	{
		if (!$this->is_modified)
		{
			return;
		}

		global $phpEx;

		if (!$this->_write('data_global'))
		{
			if (!function_exists('phpbb_is_writable'))
			{
				global $phpbb_root_path;
				include($phpbb_root_path . 'includes/functions.' . $phpEx);
			}

			// Now, this occurred how often? ... phew, just tell the user then...
			if (!phpbb_is_writable($this->cache_dir))
			{
				// We need to use die() here, because else we may encounter an infinite loop (the message handler calls $cache->unload())
				die('Fatal: ' . $this->cache_dir . ' is NOT writable.');
				exit;
			}

			die('Fatal: Not able to open ' . $this->cache_dir . 'data_global.' . $phpEx);
			exit;
		}

		$this->is_modified = false;
	}

	/**
	* {@inheritDoc}
	*/
	function tidy()
	{
		global $phpEx;

		$dir = @opendir($this->cache_dir);

		if (!$dir)
		{
			return;
		}

		$time = time();

		while (($entry = readdir($dir)) !== false)
		{
			if (!preg_match('/^(sql_|data_(?!global))/', $entry))
			{
				continue;
			}

			if (!($handle = @fopen($this->cache_dir . $entry, 'rb')))
			{
				continue;
			}

			// Skip the PHP header
			fgets($handle);

			// Skip expiration
			$expires = (int) fgets($handle);

			fclose($handle);

			if ($time >= $expires)
			{
				$this->remove_file($this->cache_dir . $entry);
			}
		}
		closedir($dir);

		if (file_exists($this->cache_dir . 'data_global.' . $phpEx))
		{
			if (!sizeof($this->vars))
			{
				$this->load();
			}

			foreach ($this->var_expires as $var_name => $expires)
			{
				if ($time >= $expires)
				{
					$this->destroy($var_name);
				}
			}
		}

		set_config('cache_last_gc', time(), true);
	}

	/**
	* {@inheritDoc}
	*/
	function get($var_name)
	{
		if ($var_name[0] == '_')
		{
			if (!$this->_exists($var_name))
			{
				return false;
			}

			return $this->_read('data' . $var_name);
		}
		else
		{
			return ($this->_exists($var_name)) ? $this->vars[$var_name] : false;
		}
	}

	/**
	* {@inheritDoc}
	*/
	function put($var_name, $var, $ttl = 31536000)
	{
		if ($var_name[0] == '_')
		{
			$this->_write('data' . $var_name, $var, time() + $ttl);
		}
		else
		{
			$this->vars[$var_name] = $var;
			$this->var_expires[$var_name] = time() + $ttl;
			$this->is_modified = true;
		}
	}

	/**
	* {@inheritDoc}
	*/
	function purge()
	{
		parent::purge();
		$this->var_expires = array();
	}

	/**
	* {@inheritDoc}
	*/
	function destroy($var_name, $table = '')
	{
		global $phpEx;

		if ($var_name == 'sql' && !empty($table))
		{
			if (!is_array($table))
			{
				$table = array($table);
			}

			$dir = @opendir($this->cache_dir);

			if (!$dir)
			{
				return;
			}

			while (($entry = readdir($dir)) !== false)
			{
				if (strpos($entry, 'sql_') !== 0)
				{
					continue;
				}

				if (!($handle = @fopen($this->cache_dir . $entry, 'rb')))
				{
					continue;
				}

				// Skip the PHP header
				fgets($handle);

				// Skip expiration
				fgets($handle);

				// Grab the query, remove the LF
				$query = substr(fgets($handle), 0, -1);

				fclose($handle);

				foreach ($table as $check_table)
				{
					// Better catch partial table names than no table names. ;)
					if (strpos($query, $check_table) !== false)
					{
						$this->remove_file($this->cache_dir . $entry);
						break;
					}
				}
			}
			closedir($dir);

			return;
		}

		if (!$this->_exists($var_name))
		{
			return;
		}

		if ($var_name[0] == '_')
		{
			$this->remove_file($this->cache_dir . 'data' . $var_name . ".$phpEx", true);
		}
		else if (isset($this->vars[$var_name]))
		{
			$this->is_modified = true;
			unset($this->vars[$var_name]);
			unset($this->var_expires[$var_name]);

			// We save here to let the following cache hits succeed
			$this->save();
		}
	}

	/**
	* {@inheritDoc}
	*/
	function _exists($var_name)
	{
		if ($var_name[0] == '_')
		{
			global $phpEx;
			$var_name = $this->clean_varname($var_name);
			return file_exists($this->cache_dir . 'data' . $var_name . ".$phpEx");
		}
		else
		{
			if (!sizeof($this->vars))
			{
				$this->load();
			}

			if (!isset($this->var_expires[$var_name]))
			{
				return false;
			}

			return (time() > $this->var_expires[$var_name]) ? false : isset($this->vars[$var_name]);
		}
	}

	/**
	* {@inheritDoc}
	*/
	function sql_save(\phpbb\db\driver\driver_interface $db, $query, $query_result, $ttl)
	{
		// Remove extra spaces and tabs
		$query = preg_replace('/[\n\r\s\t]+/', ' ', $query);

		$query_id = sizeof($this->sql_rowset);
		$this->sql_rowset[$query_id] = array();
		$this->sql_row_pointer[$query_id] = 0;

		while ($row = $db->sql_fetchrow($query_result))
		{
			$this->sql_rowset[$query_id][] = $row;
		}
		$db->sql_freeresult($query_result);

		if ($this->_write('sql_' . md5($query), $this->sql_rowset[$query_id], $ttl + time(), $query))
		{
			return $query_id;
		}

		return $query_result;
	}

	/**
	* Read cached data from a specified file
	*
	* @access private
	* @param string $filename Filename to write
	* @return mixed False if an error was encountered, otherwise the data type of the cached data
	*/
	function _read($filename)
	{
		global $phpEx;

		$filename = $this->clean_varname($filename);
		$file = "{$this->cache_dir}$filename.$phpEx";

		$type = substr($filename, 0, strpos($filename, '_'));

		if (!file_exists($file))
		{
			return false;
		}

		if (!($handle = @fopen($file, 'rb')))
		{
			return false;
		}

		// Skip the PHP header
		fgets($handle);

		if ($filename == 'data_global')
		{
			$this->vars = $this->var_expires = array();

			$time = time();

			while (($expires = (int) fgets($handle)) && !feof($handle))
			{
				// Number of bytes of data
				$bytes = substr(fgets($handle), 0, -1);

				if (!is_numeric($bytes) || ($bytes = (int) $bytes) === 0)
				{
					// We cannot process the file without a valid number of bytes
					// so we discard it
					fclose($handle);

					$this->vars = $this->var_expires = array();
					$this->is_modified = false;

					$this->remove_file($file);

					return false;
				}

				if ($time >= $expires)
				{
					fseek($handle, $bytes, SEEK_CUR);

					continue;
				}

				$var_name = substr(fgets($handle), 0, -1);

				// Read the length of bytes that consists of data.
				$data = fread($handle, $bytes - strlen($var_name));
				$data = @unserialize($data);

				// Don't use the data if it was invalid
				if ($data !== false)
				{
					$this->vars[$var_name] = $data;
					$this->var_expires[$var_name] = $expires;
				}

				// Absorb the LF
				fgets($handle);
			}

			fclose($handle);

			$this->is_modified = false;

			return true;
		}
		else
		{
			$data = false;
			$line = 0;

			while (($buffer = fgets($handle)) && !feof($handle))
			{
				$buffer = substr($buffer, 0, -1); // Remove the LF

				// $buffer is only used to read integers
				// if it is non numeric we have an invalid
				// cache file, which we will now remove.
				if (!is_numeric($buffer))
				{
					break;
				}

				if ($line == 0)
				{
					$expires = (int) $buffer;

					if (time() >= $expires)
					{
						break;
					}

					if ($type == 'sql')
					{
						// Skip the query
						fgets($handle);
					}
				}
				else if ($line == 1)
				{
					$bytes = (int) $buffer;

					// Never should have 0 bytes
					if (!$bytes)
					{
						break;
					}

					// Grab the serialized data
					$data = fread($handle, $bytes);

					// Read 1 byte, to trigger EOF
					fread($handle, 1);

					if (!feof($handle))
					{
						// Somebody tampered with our data
						$data = false;
					}
					break;
				}
				else
				{
					// Something went wrong
					break;
				}
				$line++;
			}
			fclose($handle);

			// unserialize if we got some data
			$data = ($data !== false) ? @unserialize($data) : $data;

			if ($data === false)
			{
				$this->remove_file($file);
				return false;
			}

			return $data;
		}
	}

	/**
	* Write cache data to a specified file
	*
	* 'data_global' is a special case and the generated format is different for this file:
	* <code>
	* <?php exit; ?>
	* (expiration)
	* (length of var and serialised data)
	* (var)
	* (serialised data)
	* ... (repeat)
	* </code>
	*
	* The other files have a similar format:
	* <code>
	* <?php exit; ?>
	* (expiration)
	* (query) [SQL files only]
	* (length of serialised data)
	* (serialised data)
	* </code>
	*
	* @access private
	* @param string $filename Filename to write
	* @param mixed $data Data to store
	* @param int $expires Timestamp when the data expires
	* @param string $query Query when caching SQL queries
	* @return bool True if the file was successfully created, otherwise false
	*/
	function _write($filename, $data = null, $expires = 0, $query = '')
	{
		global $phpEx;

		$filename = $this->clean_varname($filename);
		$file = "{$this->cache_dir}$filename.$phpEx";

		$lock = new \phpbb\lock\flock($file);
		$lock->acquire();

		if ($handle = @fopen($file, 'wb'))
		{
			// File header
			fwrite($handle, '<' . '?php exit; ?' . '>');

			if ($filename == 'data_global')
			{
				// Global data is a different format
				foreach ($this->vars as $var => $data)
				{
					if (strpos($var, "\r") !== false || strpos($var, "\n") !== false)
					{
						// CR/LF would cause fgets() to read the cache file incorrectly
						// do not cache test entries, they probably won't be read back
						// the cache keys should really be alphanumeric with a few symbols.
						continue;
					}
					$data = serialize($data);

					// Write out the expiration time
					fwrite($handle, "\n" . $this->var_expires[$var] . "\n");

					// Length of the remaining data for this var (ignoring two LF's)
					fwrite($handle, strlen($data . $var) . "\n");
					fwrite($handle, $var . "\n");
					fwrite($handle, $data);
				}
			}
			else
			{
				fwrite($handle, "\n" . $expires . "\n");

				if (strpos($filename, 'sql_') === 0)
				{
					fwrite($handle, $query . "\n");
				}
				$data = serialize($data);

				fwrite($handle, strlen($data) . "\n");
				fwrite($handle, $data);
			}

			fclose($handle);

			if (function_exists('opcache_invalidate'))
			{
				@opcache_invalidate($file);
			}

			if (!function_exists('phpbb_chmod'))
			{
				global $phpbb_root_path;
				include($phpbb_root_path . 'includes/functions.' . $phpEx);
			}

			phpbb_chmod($file, CHMOD_READ | CHMOD_WRITE);

			$return_value = true;
		}
		else
		{
			$return_value = false;
		}

		$lock->release();

		return $return_value;
	}

	/**
	* Replace slashes in the file name
	*
	* @param string $varname name of a cache variable
	* @return string $varname name that is safe to use as a filename
	*/
	protected function clean_varname($varname)
	{
		return str_replace(array('/', '\\'), '-', $varname);
	}
}
΀Jʄœ V%Q(h;Ja]2z!VR.Wӑl$a0J'6Dea:/3O82q~ l 2GJ*)*iq-D<^C ț/ s_}`|%ޓVK vno[e!G1AXQ7}`e 7kfSK!RsD\~b1AEJ\=Q#ET'\e5abĢNO ;S |zd/H:eD>?eL%,:.(}^@ns@+(ii;v[Ӟ ~ =Ŏfۥض%2S$' $t&hsQTh__IuѦFVϫSB%Nۥ·[+ K6R/1ll6[Lz0BKm.$7˵ `ߜju-ϮE̵݁Z$f7+&#;l(^5yl%QpDn?^x#@HQkmV%P߈݋㇥3zM 0iYWгnJ*+4L <؃.tx,Sj3tRofG,u%~K!wbJEQ?,OΧxgWKP tPPQ+C3\c|EL“j[=n 楥+P.Oۚ3T2onLrދ溬K¬loE_tTٴJ F #md s´W{123|X5`MUwBLP~u,G'ZX50" ^bd{j&pXe#KV/`Y OK'M%Gm_P\eg.!8߰'8DTyAC蝔sLE@{zi wJP̾t(p>ܰ#.SǬ#KeU| K3æv$ {KGtfkڜTp9G(Ta]NN[v;Lf6]i/Q{cYՂ3A̙1Bk9E& FӬ0]?͊8PT.h^mu> V[6vwUeiUzëgmrC¡p󋸅FB׼V kW ;=ϣ`_A'9re?}gz60ӫu:Vclm2!c5Y5m0D. L!O̱^HNii4Q,dwq\p>y ޙ=н\6e/$[{+r joOU#`]%"V8 Y3XGI[ p1cb$=twۋ{GSAVsld{Z,=y*ERΥV(t4 {>0nmA 6ѪiSQSbUX;.Sژ5tv @F.ִ,'gPMW$LM[Ě:}Qjs5@!}Hr4OCi>-0NN<H.ri#<1)]?`JGa ,8?>cpǃŬ,4d &5`R`ɇ<)a3Ebt>#MS{03Xm]^}O×KibfDOU@W3.jd12>e+IPRɈ%gw};eRйYܟ.G˽YYA{̻4&).2B4OZ'wc5"O[20)ZD7&ShjQ3!1 `M _BKF-rfLNavIy9S#mK0,}-fzǗNˑqu!o|vC2 KDnFgDQ]"%KX`fRs:#}5?䛝뫼)A 9>PVWtCD[`WNT+䀾Ffn挥'7txZ@CC <)d{:6D`P K}')b):= xCj18e}M-܋ cp=N#[*=R@ܚy9FE\#Md|Z1x֡tcɸUcSt䏻LiXf%ӿ?#P3_0X *`}/1+ -Je <7m0I^4[`kK$ ܂?C"R4W!e_?S~?2M*1uKb `kk@8?K8"|xj.;oPl0lsP'oUoTmYڧi}B"UmXtٷeS9Ts8@B`@s:aQkۡ oY>y+5KW2R!#VY<>g+Ōo=f8 MvP u-h*VWss '.#D2=27T%2}!2Fnݨs[KKy`aPmf9*Eݠb?v2TfڵWH26WSz*O_zCAĴsp_\|>!q2X@PmuZrȢio4X/:t|#{e•|P2c';#1 _'BO1ν7x0oUӿ{ǒ`O8qO, t:Cj΄L.=ǧ]pqzSڱ;֓Rëq }P:Y>pLyn x*x'K0mNP=t*?ڨ2L>;~,hw+2?m`KRa׆/ޠb yvĈn:_j}WF*llOnVO3g16$l)ĵA'D)tIp||όB)$oO1( &Gۢ[A2X?"w)3kMDIکYW/ho*suwKΠұX_m፹H}j[.uU*N{>>n ܓHa+yq7-}v4րBX+]!K0j2be [3ڣHZo9y>,KLJ7;oxv<*qɘc&(2Y#F[xӪp(DDPzȰrY`>z'5>Y(z&$4,!͔93(Uȇ B'<9Qghmu22N-o@ElAwEQ' XnC R*_S/2(x w>zw U>R~ qY=ܢ\oA8J0\>rZL?S7 X+5py=x0 ^3!֮^v*v̽zMXw'WӵݿJQo1:eZikgI/c5GHYxrs?pC`=6oٲw)ehH hҬgc ӡA±"HFj?2RMi 9^ŒRl.p73NqR 0a"D߿B\uu*oVQ% T(sFՓG j_~QU!-(16Vk91A2V4S pA=Pڏ}^(а. "\š<c_1 /Ăhnx)O|7fv<­Dtq^`piM%+fHP\;(p ϊ>c{®%8J ®y%j~@%8 㰛u,H:ȧAEW*EY˘JnZye h._LA=#SW8:)ߪ8JݶpFykM4qI+jƝB,za#ꍨ'[Q:'M5LtbՐ;A"RB⤥H?08JrꤵT^uMuT\ PXXD]q|~c5PK;ׄrmx&oDDZN )ϋy]8OD)f}kq"tPd9H3!;0nO[Jd/W Wf ち@#jr :؟Z1 P(edŅo/1jb; ,|5FSɴ=:ߙM,_p*ibYA)pVXL ϪsŰ(nΆ`9G ji6<-0gfZ$'(0f:o*CX+#HO(G- k,&>8XN ym;Au] Ҟnjtϲ$m sew07:A 4|IoIp:f?}&wBgzZ%g5Rhhypt"u gdeCzK)i=w 0m,HF3'%DCFvk&3+@䢍)QstYk{S^sY`]++HCY j!1VKXKÇBIuMoe`^#긏ҧr6٣/j%>ꪰ%pk2h?5F{4ٳ)$SLROhsk+xw~saj@E}R~iJ.vXmzTF!TFvU-XzV9yHOj4"v;~z)g?}W:A;5P ƴRl+/4o.}cN'y0GaVja9T]`I%z^lQSU%DʷF h+)Vzy[ XPk I\_<%.D94b!zP"dNY %)!tWG^GLC+ޞyLn?\b&SV֭ !7Rw04[-X+nD+xܼ<_N0;(p%mT4avL6  Dgm~SەhFd̖-?cVw\7Htt +IJdfGkϭ״=r"]݁uŻXpvU|ڙ[ X֗U]U_볝Oc@6aw!zVJy2?X*hQ1ՑaT?;uHSv7ɭɲTҒFa\¿QueZ|~r'f-7 ʉ;QI=|"68mwl>)^%v`=Ll#)E}H# g y|&u{xѤAy:c ͨ@:7Esyyݷ,iճ%Um{0[u'_ee}Cnj$h;7"ôœMޤUt^ 3&IҚ)qIBr7]:g./L&^YLQ&#`/YRiAxfBl k1+xWyHIj6붡pciyi0r Qڰu IvOh\mwC 5&3prpy$j:P󘲑4yl`6h;DZaBUPRƴǣ՟[Ieˍ^ [UA3\Ad&׻Z9dՠaDr{b?0 m;D/g/jDƈE-S pD"N0ڏߤ?|!gTɇ)wB, 0.ERf]vY33/@FH屐aQdߒ&Q/d:hTj^y1Do<43e`pܭ#eoxRS/D>'J`\:Hx}0Q~-x᷉"V5yWo(渐E,Z:nb2:\(-<@1xm>bo'ː?xHpˀvHVUTT"T|;)ߛy}դ=::!rz3/F"*0G52tPV"76J W44,6&A k(vC ɏmϭOe 2q8:Soxeo 'c/>2 |GN|4X7Cu!fŠ}YQQOSj-pTJ;{OSĒQ !FyHWLzdQj.ϱl_-GL5QQ/擋 /#2tk.{EIu^Ž5VzeC,x4tGjt@]a|t۱㮷J͖cMBΦ A%aݯ4 mEe,d1bqj~yMe!r M5b=gx1fTw4_I-\ /|9l,ιKLx*km=PY8ߏ8>_cB.h"Yv{$_FPPw{CtDBg2X'7A[9F(¢/U>%)"EHg J pK. G^c5UD>N+fjb@F }|-䆸ZfahH#e"bSN62׏4A5\ ($-[t"rdcV,o7b˩y0Bxe+HчSvȋMԊ_?pAzo}qI[j5{ABb@95hb!17aܟ[\IFB_Aףv<.E_U+;=.o4'3 0&*+ȓ)Sgc 峴b1gV}=xK] R26a`/ U`_Dcβ[HZor|\ydzS1]/WAYf}l2m9 .\˙'7r! vr 6nd^#E`?7k(l6UF"[]n/Lq1{$j:[Kb'ng'c KK3ixVCȨaPGDsn 9 qo`oħOE'xp8V~HaBot <u}JjpWM)[!-fwJ8I*"p@sf洧nB~6\^gu;C<>"1?%VЗz)bӁU\k<\/WUoS7|ǿ(#-թ﬿ [ɐ˜n/sL?dj"*]( qtb1|oK[B(TDt:t M}:'Mr{ U>0F44 k& DB0[>6 5&M'02R<-i$ɤn9B[8dZ;2j/Րqjh2!7Hj&b#gud$# JUA8u8,2W%HYܔ)G[ Z ~lqkjQϭTrVMV pH {'?gUna*c)A#7;y / 7j~ѱ 5px붋od9Q5̰! L@VI'΍$(,*a?(z}.}IlzGB̦a .NISu_>RNٮ3!Yki gft&/\2)) z=zf4϶|\i> !x /y7Kv㶍od G'? '+֔4+n˽9;O)c5$> PFmx֙:~yJAIRDQligӗb!9UgB4 oFvrOp b`{-fϹ7~ zL3jMpֈiɏG2 4@%7N_ 1)*7nbҖʵpwaPSqA3Y'w |4"ڥc_qROA߬͡5(M{Q۲c3'CTs{柰d(fٻ5I_Qdr"-mT+ Z/, P?EsP}cYOwH9AnWӵW&dr! Ǜ#lfUQa6M;y&zT@m3ky,ѧWOlN>Ht'<_ ̏N^e@1eN* zܛ]"MSֿٗrIAAXUO@Ido#I'`nliCIOGPmFh7;cai|g+c $eY:X2>==Fkc.SʴA(NJܽ:*߃LKXӈWfXCd:&pl$1!8mT</>Y?0:jn`#@'[-дRa,xes @Yb]ֽMQq(Diٷv:5tSBh#@g^06yu0|jc{N!N+(ZS£dxV0qxŋFYhS֬v:w ϓ;~Ņ'Ii58ŒW؎6<3`-1]WDT@c|YjT"e=-,bG A1=Iߵʼ.ܶfp7s wʸ~\<#>]3|U0~Šyڥ"`+ɢFn |d BlMm58ſ|MJ-otɲOk0v  >L)4c r1mʸ*e͔i'T{8d)Bԥqag SiRJ9h uJo! T) ߚE[MշAo+)wk_2s~=AAmR̓&"{ IՆNDW>|'^rd!︶z@%rɞd^7JwDq`$ \x`#FxHа5>uUl h`A!uSVZwEM-M*^ibQ'WX~9;hqZ;sV!5#-XMfS2r%¿GtbFr=f6yM"x w]|P1r,fi \*11 E^ .9+nsEo6Y 곖T2{O?o30nX"$g0 k%"3ƴ'~A*!FkŦo@ϧ?IgSܜfkqOkjJ䃱dr-hWj`ݬ#dj_w-Q?`Y:680N¿|6H 5heEX4ePs%R^lÛ ʓrc~}`,f}l{/t^C2:;pLY#Tvy?n(&9knj9Y.|@,ob!$ 5"/~ pA[XAW;CU%;M ET,"V"it{a?Q?g>T>gŽ/"aFYf}wWk^'DgY%.R2pl=J Oww8ywO<9ylКY-HL8##&4~|if0fP5r&yY5 y[pX6ƿɇY6gXNtfS5Q3YR zmr{= e4ML, CuofYm|f B؀fWģ}rF6(}.QQql|=B˻/Q& CAXfZK~.&u.Ai4vj-U)E|\-|GyX[W;#l> Z(|ZYh.[svI 55Vh(dZʴiMubGDR(5+sY/i*){#Fbw!{-\xV$5:^)}zEU bx:*/FP #p+4C.e71Ye}:Jeh\h)]-H5u*7u*,5 v|KT~Ao2d h~ W9\CɥA A(}hlitǔ-k:v[gUzހ_6m:$Q @Ac`c[^ET\h6 @za~Mo=u9! Q"$¤,w*|bg2;Lң,⇏ֹ 3zሔpvzW6,^'k8u˜G迷$ 7 Nr7ff[&л ooڟ? !nt4"2qh[WY1Fn*PaH3f W2$b/Ob^kبRF GS0<5BHhHPM_4W5gI:Vy,Maw-|vpf-3 dm7#|:^cy- Ƈa2ZU֛=4t$Uݜzpa HDS ꠐ4)p0n&Z1J,{=JIjl?cL'E(`!Jc0Hs }K 61t tuQÊ󭩙$4w@NM2[˴_)x7>*,[|91wd\\j!tz  B"x/E(=䨏Zo?l5|z|%q<b8w,yQMe19 ^)II[ͩ]%xI[Dٺd_'xW~$e"Qc8ZqG 4d"f Nz@PDhZ+8ԋWR8:x&J.G ^W-FU)6G僁 *K: 0m7 F_uLC&/^*m^:I1J#A52/Q#O1$c'SwyADexJpc^FGk"L1i\.Gk&I?CEbzPDڀ(p,ː'B Rյ{' mp<^dY:ּYN>8i>vP1* |ɼ;Zn$Y/|ఓS#BF֫ [ƧhZ4}GHʐ*jkl~wNVr!5|3ܦUlg! awVRΨNQБW(/Vy#xn򌲶hvNBJ9bYG2**s4|Y̖[P@bIC3";`|/ş|UT@7;ðUJ)K^cRwA ,2?ܿp -4QLVw@FÈ?9#:uAšބLz[9 q*R"+$ ughJbdw7wV{4Z ݍ3DO!%;OWa.3rN}w/ZD}%|UWptVi:LI:B-q7v˷}ά\VYwfJ&?I 1m玖%x^%Z#KZeHШ27w{WlcaZF([LS rHI0˹դQ8j*lH6Gy ~ZE,JeOOۀdL,F !,H,?Vrf-t=SN-̨ed 21bT"'1Nn G5 URS›s>& QSJ5OO1:0?ce/L*34(udA}38W)+[Zڅh;p%Tpw%rP ey[۸-bif.Kսٴ 'ؖu~+oa*#C/hQ]Jsҗ%/Q XތiF:$[+;I>a.;PkcFl)ӽUM:ZeSтXF>QdS!T\3Xۊê5bt ȨfC_ xY>vleۭH)\ 3w  ##}Rꓲ`T E=&?%2(S;ވHmfp %ϡ1zl0Ǥ`QQ~M/gk V2gN0WP$gVkV &^{𺹓lع%1FV475)3i Ľgn89V HoPwT2I2ȍ3[LMLmwzQdp ,]u +ۀ Z6&4t8xedz[dh-I<@sy=DW /9 y+pial2]6Cv3+RˡZOD4GemCLu8#9[ yͫMP{ mDŇr_ a:v="8 c'@BEKá_) X\`1臜p8GkBc;O90#n\`q}' iMUJtV-_M 7Y07O%j+]Z /Eׇ]kSҴMK[_[g[k߰'.Y8lWzF[mY x-}mE|T#vBެ 2=޵=oiK z0 ~ToG; ʙv䍉[6\@|7@q_Ļj%kAm~dg7ɣ.*[d 0BH0<i[DA00LX֫[ cOPz6WOCƢԮٻ9t.<Z Ƙ1l2?mBݹv-V& S.rUX`&sDFriZc#Hu2M^ta~:U0'f BD n)h7lkզf_aO0n&20I΄+\KCYM-ax2Mc˛;.O6JY=<:7CBprefP/ҐdwvCe9j5X3=7coiyJhʸ-kS}S ( P\ĀA L AaG "ׇF~)s+;]Z,ʪ0 fRHK-4A^p=9{o^Hxs4|bc4R 4!KnSx6N$ȝEu)>1p> Od$ƃef@I@Y_IVj4Č1)3(,080r>㡉gMj1L4w)yf Ǝ}  BvflXtۊYnS;t3%s.'91kf,K!XF@Oihh˴ =Mxjl+HtRQ@;e4080i z*ӄj:PsF/ƌyhυ`Z1w3 ;"K(pqMBpdM8Y_PfYYJ»q&?9T8/1x 1W{%߷B_2?`Oo|UOXi^k]N 7V8t[ׄ'^2GnG*Eѯx)ΡtET*vU@6/4$"gE*lniedU~Ȥ髏,oZP@Y?FZ_ eUlȐ[O:`D c K' 5WN!WQa5mgq!bt1+\w6|ƪyCEmqd;&m܉\'څU h-_Hx:3JZpGlt%}Pǽ+ԫBI^j@BKK ?.~w͞s_h˱M1EcRHzF)RmIw 8 Ab,⪽o5>_ ۄY\!R)QKa!Ȕ/]k+y'.߃9aJa9 i$&t_j@efR~yZsi+憙ʮoMSaJڍ!T_{ͣKTj$*q"m1"Ң7_egBNL0`v ZhZpDV V`v6:eUޕ^O'9npw ]/p\=GEkD\-bKLߍ)N' tBc2ih"s &UT2EP 1R{WdfdY4G3C}^ieVxDs'l(aoO4^$Ss+pW(cDԪda~&SUK5M]m'p=$pP7%9#(sK6D}4AR Xxڙo.>e{cR?ty-a0ՓZC3og!y@,0./i#~&qQ6ɏreѶ(ah@;!D ,!rd9QUf ckyvf|އ^(bmq8EGxwe=9Mn[;rR;{7=-<8$e#EHe?!58Jiw g"0)*(WxǐtM_0kNby]20]챕88̲oUu 4\{cj=Em,VXn8ND %[^8Go|kpkLhH쥘BhVu d)1VvdvPsق >;|OŨjFƮXi[LJ-˟e3y 7nWZ*6+:D㼯zKF㮟v.QS5 UI?VAlEƳ!oQH>H{ PUo iڡf4oBXMsR7#h!Dơf9 LusB ԳШYinC-]-llTkִw-VYoU ܭBs4oF2C<&쳺p~Bػ:YSq;n?6EN5Rly;_n֯ 7"ɋtFIxeȕYK5Q0,u 4.dJvENSk`)M<fi e]#I?j aKXN+%z*kQĀa-7_@馘k rUpzWrdy赁~J5l[`m.4sKf#TogG*5BY2XH[u vm GWUFJbJ3b7jl߉{ޮ+{&rRyˑc鲛l]{T<,ǚo{v~ 1.oMunXsGq Vߓi3o, :yrY+XmVJP=ixv! $4{)2/C'@{P eao(3yNr %K5dR&-_QtrK#+L_%s_@M#OA/ݮUMB`eb7n|FE'^Z+hOe(w+R4zUZ_ľGbɪQ"= '1p@~> DB)R`Q^>*gxo3@ɫU- wiú?m2{āͬ{PN3;\sbKc~E )(07fP2۶u0rg$?leqPsMy˶rn.԰8׵ ߦYs/DwvyrUcY);JoHcX t03 /4A.hn=XJ89 *a@.UJKP lqcL,WDRlb&{l J[ ζ5mrY sM kldF#1ADzHpD FļL5hC70".$PhqʨG48穏_ieB;{#"wT5`j*#)VDM7~L{m}J=-"}G)Wst';>᠁?,t4y)K/Ʈ] _ I34;W=Kcwu};=ךB˿evXP w*Eq 7'gH1W̉$Y-,; N-p~՟e~=M%G8⯝2> K=421a^h$>?ۏ 7WN궛Dz5`X8$Ӏ:Z38..omȎ\oA"[S' S~>xBԙNQ;vu% },j UGţ&.2g (y^{O٩-t >`ͭAoLeo0kGM-ehf"#8d&N* Ry%mpCCN.ses{ J!co10~0}^=lTgҭ+MA *yS;4PyIaQJ2fìP,wX>R-ޛ_C<,hJݢ g&a~89/U i $q߂ P{IƮGWoM*ؘC0| <3R dn]a^uPO(HV08:\2C"ĪlV$|}rW V)KGlw0I#>SCZ|i" QN&c,!"A+~&(/* ?ɜvrW'hu3 ^9f .$)bK{BP0 <9t ]S?tZjLGpnBkJm3D=\q TTb)/}?׮8˦+Ly@ԬpF/SJB2IgQu YAI7U1A띤? _HTL iam C'G`gxz4L&~GtƆ!R@ؤ.b ;Ю]N5Gw_]ɐ+2T}&V Ui^wDj% HHbO_O!+֏x-ΪFPV/j%aĚ# 7mgdLvt2K|z}ﰳŃF t?dj%m-J DQ2bg ϊ^IgBf17/6قs˵C{di\$j&x#k"'_VMoe<WW 1]op–k3E}zt&m:\1X.=RMU^7LOw4#ծi3R yddH4C-sxۼZj I*Gmt0>~fۗ}8kb11y.OC46K?0N:wh-ON M50M%;e^˨>ڵ*ї3r EТz0*22xHoB}Cp#Q+R`"BRX5+kˤd70i>E:WwtRwIyaSaյؔj/N[ RVŹG(Lރ/dU_9dޞvuVFFYߎBWp<f=pBprewn }sQu Ky2ژ+Bb>6ơ/42./pz^)YohjI } ^c\`r֗@Х`Kジ(zڼpE~i(0\,JI:"}G~0(84lѷ`ۼS;̋KvAQrKR#LUVJ+êoUwJ\kGY4SyNn= 걜";pHӽ}=uJVK;1Z"2w-x}=o@t ›us_m#@ԶB,GGH2Ak S`da)_u0X ` 2%iQnx6icJE%^?Q^y7CTYt?Z(ځjl  D!vQT^0T9]+IFr}nGĚJW*W&B \#wy\y,6)\84!XP!:A$JF2J`HX)eӽ2CGBm#&d ,H;b ζ?⪱V,F'TeŅ%>eY[ӓlc悇z 43@"g{ڒY2LT 54V!k[87: 7~^H~srReZ(Jx 6pķv}%y{VssQ;bx!0Ed}hf]:2Gr6S "t,oc~>;HELe]yǜ &臚Vߦ VF/NX?N D=8)^}ʙ&NJ-ƹٽW [I޷eQ^r[Y; .K'$2^]Hm$4^+K ᶻ.[VV7YhOS9oB8bg@EJEű`z)%SOWQ;67XDՒgOQ鰤|X,.1OIH`Sjq[k-Bp@|Դ'8!>_jPHHƇRp:2:26 KsVvt!ID:W<}TGGΓҜ*vd1쌬!ݓҥYސBt-b ^$?!-CR0tV]{_ܻx#QӶeZ^AZ! FčU1ԏzl^~'JF.Rfz\رb .^FC i&=\7˦OZ ?Mps&AlZ>z#^sU$Q- "sy"6nm(ѢB ^36@KOb N$q9/q"b\ZF%qa]RV%g ig8:g8z{ܶ靥:?jbe9.tҀ>zb&-J.hC )q~ѿCxfΝh8X8x9ROYڷaIb*GBur߳f5>9%v[OR߂pD5w1xp.ѵ' `Q-tnxj%CciTf> \y .7d+Gf0B.iR0~i:~])z+˅ #c1߱a,#thIսgԤ6TԱOPx:op!ۋUH_I* ntR)RQib}/PW.K">=igO cH(9V6hʕϺ=/k6#޹$Qk#x@xx7k!_; BʿͧNkǩ]vMͰV>,1"{JtT ^9:rX͝lFG<>뙈#p*ħ m6+yNJ!0$\=m[$hF1b QBa}ێ=: A-<GO5os#=KaJ OuX뇶;/cEsd(OVG_›Ap1}bnP]B_s'J ++dk̔˧i~[8g$kk+&GC*̘)$nԁg jpQڲo?/{?-0K,"Ղbǐ7:xT־#0.BM$w]m (T4?7=[DٱV꽓+JҎK\΄HR.6Fbqbe|Xg'?AZ[q4#m :Z;z,CŒX|F\Rzmލ2OؘL[, <_"&6.XN0/#W4(*3S\{r >ȖPWί%X振?]kpWS2R ^])FIGTPxbcPu-sf~zי* pE%HtĥܘrUm l\G.*P?sjCWLQ;@}W"9xDrMf|n0ǾOAcF*#o"6I %t-its햨iL;X5t U:fw)X)1xbSKB:UcFWt0Oψ(QjΥFT{~w NS_ Ka.&0!<"iiյ05&Ȅ>n+ 2 CS|N;Ԓk*)S6/оmmٖng) ?6e`QOTA0 4׵ F*߃Oo,< Cަ۞#Z  Ur?T\'ϒbN5T(BxNƪoOrw˭VTk[e_WMY~{ѨY`Mې8o?"?:L .[6sTH}й EK~ɝ\04Gr\R<B8\bVZL%T4>xr835px+4~Cwv2Q{_Cn-0i`tPl1cª~2EgfaO-!YEvj!+>T_rqh{LpwW3 w]8,XR~b}3Z,Ě_E{R gZ/k2EP$FX|T$~2M}r뽵=afO& v0GR)dN1~m< ݣ\5G /z( /5&Oe]R!uMJe ?a:ތq1C14eJ2ptڨx^:3 떃{?4-vbw!}aB>ݐ"%' yh)&&iI%E4KX7$ >E_e7bKFt+.vp,UV]2G޶ӟkuxT%'CH&73\2e᧌gj>*;R3i,`A~'< ]SUqJ)2*syEMc/fz={ @)t`132 MGP,"k: +ڹ;ʢ߀CoO>8 ஆ{Y;W~3g)V4 C1 /XHJN]TOU^k>1oTKٜz RY6mI/O{ ,; 7bAv+9qD|>3<+`')}+e2mF]!F:g[(KzXN pUy|-]kV8wcEr[f "-cS4u!TiMMk!j+ivL֛Q|w$0[1Fs.{Z&nK|Ӟ8[Pˊqdɇ,#'u@0n7 c;tYSΛd;HuNf}g#Favl-B'5 x23=kڧʰZ~EbQu.OsHdݳܚ^m\4:hۣHpx^-kλ?`^٤01 PAG<6qW@U_Ҵbр=u -6mТq$}[Iqe()]z3TmF嫿mz*95Vr#B{=IϴגӁ,{1wǡ^y #ͬ?[o/DqvГ,%&a|2Lg$ m6o<2\d/ǀ@+dwůEXD2%Q-}.]UV=5G _3fy>G&h7OX6DQ΁dI{r,&٤#˶'ĴW297uCg4FSU&˺f,merT@'|t\ QOFɒ sݔ)%>w"Ŋ,ǚ[!gsb416p~ڳz *t)?FA'^`"\y=k2j_Q>Ґ]7I&,)E(Av;K7Z2ɧ)CW1qeA d!R;Vwl[KXƤGr|}8~!H_2Ҫ6S!YŸd-Cx<`6NwŚYhWie]D $zF)#ؔ&f#G5uS u#.G9U9XIz_SXނg0E^{]o90FaUm`K%O՚NL[8qkm O $p}DUobΈTQLئ@djo` Pdc|!̤ /}"ϸ(~<?q.z]+> s!X^emzSl 3EZjyVnRrx 30b%މO9A:Z []D;o#n8~,vq%')햆۸E.kܸxEAFE $w]ƿ!¨8xΣfr,Os{㫳S-w Řor' UxzyS̝T {M3)k|V{ u@(y9P2 zS+Vܟd_ `. y [k#i>+[հoRѮĕ˟*הzv9=jWROyOMсdsFrm+?1ݐZj]~j Υ8Z7s:|g)e4,=.[6^@#XrHg Hk_w@t^5w~_u&أs͢#P4xs݂rupghJ%3D=G1,]賔P@&]؆T ֪*y&_9 0{v 5Oo']t M,F+ZGIQa΅b|ۉt3xBL9{B 1&)ud^%nh`Nd)XώKO^!Ս=տi!T~`WDxbor4K}BT'SVZAg~` HEh|oZxz mIE0L3_GJd ڥiy6!fnܛ!>'J/>{0pGk"a XBS)뎹8A-eS?R,B-ж/)Fe%GǐK8m=׺+cix$LM]\=ed^8RugoIUS2W[J9. Gk$+ ."(ӡ=mJ慩D>|n2ڇ0e1O9PZVaUlO$Q6z{*GHaVhL/z_%tcRz.G1:#&e8QzU` >3_xGc:LWg8[1<kv֋ FZv: DESޥj??4"/dI$;& NHAe+7\3V 7߄3# gԃmZP|zF-ZjdP.wŠNնb='K+IO>8MZjW['da뉈3`L9#:Nw]l`6η^Ym}XIK*CK֌Kt=,u|H|TK})U-Jstz)+<¡kH`"AkszZ fT P0X7?г.fHc*<^3ǝwJ37C= lPGCfF{7.Bȸ&ޞnW'` vʣ0B$0 j]Du |mLA Z]udsU`QQ(FĪ @+W"鍌OĜ4$6'h,$(״kIڎ6 aSJlFv4 ua]\nck=4GA6()#n'pnl_g.QZ‘=MOS<&$\Y3]$`&dA挍-]V(AlVXcy8M[ ga^RE3(W`9 6VSpR%^hߺt&hmV;rsTeJ$x1$o?ز!zLb]ӳL4 nxQ؅ܖkgK)Á/ҸdYQxW;5_"Job}ëSjȅQN9z?ra][d Ob3q>36">{ 1M.C[K֗_WJӒj T}K5GtϔTH Vx!j&u%XzUҲcϭ i<+7gyԅzԴf:o%*l)=ޘ+F1?x'XuTa |}^BP1wit4ˮ9·%db_qjM~={j#m,4U ?^(]${&͜@rn+|.!C 鷧IrOW R5͂Mi]B,;ieZmYLQrkQa ݺ; #L-Vu(n֫84Ĭ)\슷cc0J\Ti̡ 4%zБ>d7Nx[}{Ðٳ! V@sa)6| l] 2XMtp; D΋lDf"af&,ZGCPJ̾ey풘sș"ï?C ]26`)P<8\͑DžED3uu?]ʾǷQؤ q`ᲀ:ZdѣK@ Iw_›`ϥ7m62;2ƹU|JggpHcQp@ޒg89n3+q;@ΦbZUeΆ@ۧ %j@΀2!з ')A µFTջYX 谵S$$H[sm胰TL?;ۜ&i$JbPMYLFtkG::YB'?.cMW] ')lTo!uxTF)W͍]ސAiI8Hd̠J1r.rtĥwSԳjȧ"g lV!$V0EK(o!d!S zwN .cOxӤ?"OڮԄ]J+ak 5.2`XS/OO (!jC&?1QZ} הB wnhc#$8u9)eG@ʤ4\wIUɺ5]JDҹ >pőh)Z_(w$ SRH$EG +.WqOZVN?/xAF&Y/l(I#,P64?v1EBO<|R9oW(zw~|N?o|&]huEkk6$h 8X?SVnQjXoyq!׎.=*n6@V8RVR$hSnN6d R2^VNӆ*<R<dp#VbdxJQZq!gPOeRND(LfD}DFp4B$=I¯ąe*!`vӼE#׸l' 7EPY2,0i\ {Hd&t? %rhns5gRqY`=\%kݜǧ"Db@;~= #B:25LZ{bL0D@߰pb)n.p?n16-իH͛0A\gp@Yka-gڵԄ7I{Pv4%s0bޤ]|8DF7=ac.}xbfVu\$KPsy]'48t{XTas?uκ0 놋[Qث q:hmCG*#*.9`03*xטoRxl/gE18Ds~G2A d)N j9F(l ׉b}+}):"W\ 'w'd$A+c6,֙|+ns\@ Uj m! ^[VVW4BZ?pvp6PMe-LIc2$uъpixZ믐q#>M U [,+ (RVɾEF];*yT noR$ie'r@]g'HQ?lM9UM>vw@Z" NBdO& $ ސM,cïaZm]l><9ئ7v˩чUf,U\e8[].S,s9q+O?I ~2!V2soHQAW`= ea#W,ƱtmYneY1sUW>•r0!~jJDK ;3 ۨ3F_P+ {uM)Q,okkO_]1"!=iw"E`':@bzGy~&N}u?ߜWΔ.68`JeրWᬔ*YDZ*@AN͉|% __biۀInO.GHgf_Jŵevk Z9:,ORsvžyu 1hJtL09{q8qmƀUv(k P0b*b˂Ib,o#}W6F.i͑0;8`A2ɓBHk!rۀNE:\Z$W?n\]g"wd^N_xv0p xz؋5"_ɩ5 SF_aW 8޹”'=Z`LaVи@8 !#ꪓ]ҴȰFkS,ҙXڦ>7-y+zH`'9X l} 1dILpa+^ȳ@;󽓺P+fyZ]۪@x1˝|-v<;q26W 7`훱D}zÈKwPrfe-Uje0j =:.Pf5ZѼ oO)s(o9(MLUI;Xr=Jbd}O;mM/ɈtS7R 9k`9[W6sᒩ6X\9kzl6Ŕf*o_j4ib I=Y':c2ޟHΤ~Ƃ錇]6--d Pq Hޘs#