rack-cache-1.2/0000755000000000000000000000000011745752772012054 5ustar rootrootrack-cache-1.2/COPYING0000644000000000000000000000204711745752772013112 0ustar rootrootCopyright (c) 2008 Ryan Tomayko 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 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. rack-cache-1.2/rack-cache.gemspec0000644000000000000000000000420711745752772015405 0ustar rootrootGem::Specification.new do |s| s.specification_version = 2 if s.respond_to? :specification_version= s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.name = 'rack-cache' s.version = '1.2' s.date = '2012-03-05' s.summary = "HTTP Caching for Rack" s.description = "Rack::Cache is suitable as a quick drop-in component to enable HTTP caching for Rack-based applications that produce freshness (Expires, Cache-Control) and/or validation (Last-Modified, ETag) information." s.authors = ["Ryan Tomayko"] s.email = "r@tomayko.com" # = MANIFEST = s.files = %w[ CHANGES COPYING Gemfile README Rakefile TODO doc/configuration.markdown doc/faq.markdown doc/index.markdown doc/layout.html.erb doc/license.markdown doc/rack-cache.css doc/server.ru doc/storage.markdown example/sinatra/app.rb example/sinatra/views/index.erb lib/rack-cache.rb lib/rack/cache.rb lib/rack/cache/appengine.rb lib/rack/cache/cachecontrol.rb lib/rack/cache/context.rb lib/rack/cache/entitystore.rb lib/rack/cache/key.rb lib/rack/cache/metastore.rb lib/rack/cache/options.rb lib/rack/cache/request.rb lib/rack/cache/response.rb lib/rack/cache/storage.rb rack-cache.gemspec test/cache_test.rb test/cachecontrol_test.rb test/context_test.rb test/entitystore_test.rb test/key_test.rb test/metastore_test.rb test/options_test.rb test/pony.jpg test/request_test.rb test/response_test.rb test/spec_setup.rb test/storage_test.rb ] # = MANIFEST = s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/} s.extra_rdoc_files = %w[README COPYING TODO CHANGES] s.add_dependency 'rack', '>= 0.4' s.add_development_dependency 'bacon' s.add_development_dependency 'memcached' s.add_development_dependency 'dalli' s.has_rdoc = true s.homepage = "http://tomayko.com/src/rack-cache/" s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Rack::Cache", "--main", "Rack::Cache"] s.require_paths = %w[lib] s.rubygems_version = '1.1.1' end rack-cache-1.2/metadata.yml0000644000000000000000000000672611745752772014372 0ustar rootroot--- !ruby/object:Gem::Specification name: rack-cache version: !ruby/object:Gem::Version version: '1.2' prerelease: platform: ruby authors: - Ryan Tomayko autorequire: bindir: bin cert_chain: [] date: 2012-03-05 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rack requirement: &70248834703480 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0.4' type: :runtime prerelease: false version_requirements: *70248834703480 - !ruby/object:Gem::Dependency name: bacon requirement: &70248834703100 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: *70248834703100 - !ruby/object:Gem::Dependency name: memcached requirement: &70248834702640 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: *70248834702640 - !ruby/object:Gem::Dependency name: dalli requirement: &70248834702220 !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: *70248834702220 description: Rack::Cache is suitable as a quick drop-in component to enable HTTP caching for Rack-based applications that produce freshness (Expires, Cache-Control) and/or validation (Last-Modified, ETag) information. email: r@tomayko.com executables: [] extensions: [] extra_rdoc_files: - README - COPYING - TODO - CHANGES files: - CHANGES - COPYING - Gemfile - README - Rakefile - TODO - doc/configuration.markdown - doc/faq.markdown - doc/index.markdown - doc/layout.html.erb - doc/license.markdown - doc/rack-cache.css - doc/server.ru - doc/storage.markdown - example/sinatra/app.rb - example/sinatra/views/index.erb - lib/rack-cache.rb - lib/rack/cache.rb - lib/rack/cache/appengine.rb - lib/rack/cache/cachecontrol.rb - lib/rack/cache/context.rb - lib/rack/cache/entitystore.rb - lib/rack/cache/key.rb - lib/rack/cache/metastore.rb - lib/rack/cache/options.rb - lib/rack/cache/request.rb - lib/rack/cache/response.rb - lib/rack/cache/storage.rb - rack-cache.gemspec - test/cache_test.rb - test/cachecontrol_test.rb - test/context_test.rb - test/entitystore_test.rb - test/key_test.rb - test/metastore_test.rb - test/options_test.rb - test/pony.jpg - test/request_test.rb - test/response_test.rb - test/spec_setup.rb - test/storage_test.rb homepage: http://tomayko.com/src/rack-cache/ licenses: [] post_install_message: rdoc_options: - --line-numbers - --inline-source - --title - Rack::Cache - --main - Rack::Cache require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 1.8.11 signing_key: specification_version: 2 summary: HTTP Caching for Rack test_files: - test/cache_test.rb - test/cachecontrol_test.rb - test/context_test.rb - test/entitystore_test.rb - test/key_test.rb - test/metastore_test.rb - test/options_test.rb - test/request_test.rb - test/response_test.rb - test/storage_test.rb rack-cache-1.2/test/0000755000000000000000000000000011745752772013033 5ustar rootrootrack-cache-1.2/test/options_test.rb0000644000000000000000000000436611745752772016123 0ustar rootrootrequire "#{File.dirname(__FILE__)}/spec_setup" require 'rack/cache/options' module Rack::Cache::Options option_accessor :foo end class MockOptions include Rack::Cache::Options def initialize @env = nil initialize_options end end describe 'Rack::Cache::Options' do before { @options = MockOptions.new } describe '#set' do it 'sets a Symbol option as rack-cache.symbol' do @options.set :bar, 'baz' @options.options['rack-cache.bar'].should.equal 'baz' end it 'sets a String option as string' do @options.set 'foo.bar', 'bling' @options.options['foo.bar'].should.equal 'bling' end it 'sets all key/value pairs when given a Hash' do @options.set :foo => 'bar', :bar => 'baz', 'foo.bar' => 'bling' @options.foo.should.equal 'bar' @options.options['rack-cache.bar'].should.equal 'baz' @options.options['foo.bar'].should.equal 'bling' end end it 'makes options declared with option_accessor available as attributes' do @options.set :foo, 'bar' @options.foo.should.equal 'bar' end it 'allows setting multiple options via assignment' do @options.options = { :foo => 'bar', :bar => 'baz', 'foo.bar' => 'bling' } @options.foo.should.equal 'bar' @options.options['foo.bar'].should.equal 'bling' @options.options['rack-cache.bar'].should.equal 'baz' end it "allows storing the value as a block" do block = Proc.new { "bar block" } @options.set(:foo, &block) @options.options['rack-cache.foo'].should.equal block end it 'allows the cache key generator to be configured' do @options.should.respond_to :cache_key @options.should.respond_to :cache_key= end it 'allows the meta store to be configured' do @options.should.respond_to :metastore @options.should.respond_to :metastore= @options.metastore.should.not.be.nil end it 'allows the entity store to be configured' do @options.should.respond_to :entitystore @options.should.respond_to :entitystore= @options.entitystore.should.not.be.nil end it 'allows log verbosity to be configured' do @options.should.respond_to :verbose @options.should.respond_to :verbose= @options.should.respond_to :verbose? @options.verbose.should.not.be.nil end end rack-cache-1.2/test/request_test.rb0000644000000000000000000000130511745752772016106 0ustar rootrootrequire "#{File.dirname(__FILE__)}/spec_setup" require 'rack/cache/request' describe 'Rack::Cache::Request' do it 'is marked as no_cache when the Cache-Control header includes the no-cache directive' do request = Rack::Cache::Request.new('HTTP_CACHE_CONTROL' => 'public, no-cache') request.should.be.no_cache end it 'is marked as no_cache when request should not be loaded from cache' do request = Rack::Cache::Request.new('HTTP_PRAGMA' => 'no-cache') request.should.be.no_cache end it 'is not marked as no_cache when neither no-cache directive is specified' do request = Rack::Cache::Request.new('HTTP_CACHE_CONTROL' => 'public') request.should.not.be.no_cache end end rack-cache-1.2/test/response_test.rb0000644000000000000000000001473611745752772016270 0ustar rootrootrequire "#{File.dirname(__FILE__)}/spec_setup" describe 'Rack::Cache::Response' do before do @now = Time.httpdate(Time.now.httpdate) @one_hour_ago = Time.httpdate((Time.now - (60**2)).httpdate) @one_hour_later = Time.httpdate((Time.now + (60**2)).httpdate) @res = Rack::Cache::Response.new(200, {'Date' => @now.httpdate}, []) end after do @now, @res, @one_hour_ago = nil end it 'marks Rack tuples with string typed statuses as cacheable' do @res = Rack::Cache::Response.new('200',{'Date' => @now.httpdate},[]) @res.headers['Expires'] = @one_hour_later.httpdate @res.should.be.cacheable end it 'responds to #to_a with a Rack response tuple' do @res.should.respond_to :to_a @res.to_a.should.equal [200, {'Date' => @now.httpdate}, []] end describe '#cache_control' do it 'handles multiple name=value pairs' do @res.headers['Cache-Control'] = 'max-age=600, max-stale=300, min-fresh=570' @res.cache_control['max-age'].should.equal '600' @res.cache_control['max-stale'].should.equal '300' @res.cache_control['min-fresh'].should.equal '570' end it 'removes the header when given an empty hash' do @res.headers['Cache-Control'] = 'max-age=600, must-revalidate' @res.cache_control['max-age'].should.equal '600' @res.cache_control = {} @res.headers.should.not.include 'Cache-Control' end end describe '#validateable?' do it 'is true when Last-Modified header present' do @res = Rack::Cache::Response.new(200, {'Last-Modified' => @one_hour_ago.httpdate}, []) @res.should.be.validateable end it 'is true when ETag header present' do @res = Rack::Cache::Response.new(200, {'ETag' => '"12345"'}, []) @res.should.be.validateable end it 'is false when no validator is present' do @res = Rack::Cache::Response.new(200, {}, []) @res.should.not.be.validateable end end describe '#date' do it 'uses the Date header if present' do @res = Rack::Cache::Response.new(200, {'Date' => @one_hour_ago.httpdate}, []) @res.date.should.equal @one_hour_ago end it 'uses the current time when no Date header present' do @res = Rack::Cache::Response.new(200, {}, []) @res.date.to_i.should.be.close Time.now.to_i, 1 end it 'returns the correct date when the header is modified directly' do @res = Rack::Cache::Response.new(200, { 'Date' => @one_hour_ago.httpdate }, []) @res.date.should.equal @one_hour_ago @res.headers['Date'] = @now.httpdate @res.date.should.equal @now end end describe '#max_age' do it 'uses s-maxage cache control directive when present' do @res.headers['Cache-Control'] = 's-maxage=600, max-age=0' @res.max_age.should.equal 600 end it 'falls back to max-age when no s-maxage directive present' do @res.headers['Cache-Control'] = 'max-age=600' @res.max_age.should.equal 600 end it 'falls back to Expires when no max-age or s-maxage directive present' do @res.headers['Cache-Control'] = 'must-revalidate' @res.headers['Expires'] = @one_hour_later.httpdate @res.max_age.should.equal 60 ** 2 end it 'gives a #max_age of nil when no freshness information available' do @res.max_age.should.be.nil end end describe '#private=' do it 'adds the private Cache-Control directive when set true' do @res.headers['Cache-Control'] = 'max-age=100' @res.private = true @res.headers['Cache-Control'].split(', ').sort. should.equal ['max-age=100', 'private'] end it 'removes the public Cache-Control directive' do @res.headers['Cache-Control'] = 'public, max-age=100' @res.private = true @res.headers['Cache-Control'].split(', ').sort. should.equal ['max-age=100', 'private'] end end describe '#expire!' do it 'sets the Age to be equal to the max-age' do @res.headers['Cache-Control'] = 'max-age=100' @res.expire! @res.headers['Age'].should.equal '100' end it 'sets the Age to be equal to the s-maxage when both max-age and s-maxage present' do @res.headers['Cache-Control'] = 'max-age=100, s-maxage=500' @res.expire! @res.headers['Age'].should.equal '500' end it 'does nothing when the response is already stale/expired' do @res.headers['Cache-Control'] = 'max-age=5, s-maxage=500' @res.headers['Age'] = '1000' @res.expire! @res.headers['Age'].should.equal '1000' end it 'does nothing when the response does not include freshness information' do @res.expire! @res.headers.should.not.include 'Age' end end describe '#ttl' do it 'is nil when no Expires or Cache-Control headers present' do @res.ttl.should.be.nil end it 'uses the Expires header when no max-age is present' do @res.headers['Expires'] = (@res.now + (60**2)).httpdate @res.ttl.should.be.close(60**2, 1) end it 'returns negative values when Expires is in part' do @res.ttl.should.be.nil @res.headers['Expires'] = @one_hour_ago.httpdate @res.ttl.should.be < 0 end it 'uses the Cache-Control max-age value when present' do @res.headers['Cache-Control'] = 'max-age=60' @res.ttl.should.be.close(60, 1) end end describe '#vary' do it 'is nil when no Vary header is present' do @res.vary.should.be.nil end it 'returns the literal value of the Vary header' do @res.headers['Vary'] = 'Foo Bar Baz' @res.vary.should.equal 'Foo Bar Baz' end it 'can be checked for existence using the #vary? method' do @res.should.respond_to :vary? @res.should.not.vary @res.headers['Vary'] = '*' @res.should.vary end end describe '#vary_header_names' do it 'returns an empty Array when no Vary header is present' do @res.vary_header_names.should.be.empty end it 'parses a single header name value' do @res.headers['Vary'] = 'Accept-Language' @res.vary_header_names.should.equal ['Accept-Language'] end it 'parses multiple header name values separated by spaces' do @res.headers['Vary'] = 'Accept-Language User-Agent X-Foo' @res.vary_header_names.should.equal \ ['Accept-Language', 'User-Agent', 'X-Foo'] end it 'parses multiple header name values separated by commas' do @res.headers['Vary'] = 'Accept-Language,User-Agent, X-Foo' @res.vary_header_names.should.equal \ ['Accept-Language', 'User-Agent', 'X-Foo'] end end end rack-cache-1.2/test/key_test.rb0000644000000000000000000000255111745752772015212 0ustar rootrootrequire "#{File.dirname(__FILE__)}/spec_setup" require 'rack/cache/key' describe 'A Rack::Cache::Key' do # Helper Methods ============================================================= def mock_request(*args) uri, opts = args env = Rack::MockRequest.env_for(uri, opts || {}) Rack::Cache::Request.new(env) end def new_key(request) Rack::Cache::Key.call(request) end it "sorts params" do request = mock_request('/test?z=last&a=first') new_key(request).should.include('a=first&z=last') end it "includes the scheme" do request = mock_request( '/test', 'rack.url_scheme' => 'https', 'HTTP_HOST' => 'www2.example.org' ) new_key(request).should.include('https://') end it "includes host" do request = mock_request('/test', "HTTP_HOST" => 'www2.example.org') new_key(request).should.include('www2.example.org') end it "includes path" do request = mock_request('/test') new_key(request).should.include('/test') end it "sorts the query string by key/value after decoding" do request = mock_request('/test?x=q&a=b&%78=c') new_key(request).should.match(/\?a=b&x=c&x=q$/) end it "is in order of scheme, host, path, params" do request = mock_request('/test?x=y', "HTTP_HOST" => 'www2.example.org') new_key(request).should.equal "http://www2.example.org/test?x=y" end end rack-cache-1.2/test/cache_test.rb0000644000000000000000000000202211745752772015456 0ustar rootrootrequire "#{File.dirname(__FILE__)}/spec_setup" def dumb_app(env) body = block_given? ? [yield] : ['Hi'] [ 200, {'Content-Type' => 'text/plain'}, body ] end describe 'Rack::Cache::new' do before { @app = method(:dumb_app) } it 'takes a backend and returns a middleware component' do Rack::Cache.new(@app). should.respond_to :call end it 'takes an options Hash' do lambda { Rack::Cache.new(@app, {}) }. should.not.raise(ArgumentError) end it 'sets options provided in the options Hash' do object = Rack::Cache.new(@app, :foo => 'bar', 'foo.bar' => 'bling') object.options['foo.bar'].should.equal 'bling' object.options['rack-cache.foo'].should.equal 'bar' end it 'takes a block; executes it during initialization' do state, object = 'not invoked', nil instance = Rack::Cache.new @app do |cache| object = cache state = 'invoked' cache.should.respond_to :set end state.should.equal 'invoked' object.should.be.same_as instance end end rack-cache-1.2/test/pony.jpg0000644000000000000000000065265511745752772014545 0ustar rootrootJFIFHHExifMM*bj(1r2iHHAdobe Photoshop 7.02007:03:16 16:53:26(&HHJFIFHH Adobe_CMAdobed            _"?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?ek5{ᯱx":,~="7UCm{ˁ]D,( 0;t[ m[%mm@[vC&q=?~ϺDzc`^;{no~k,z"gS-<zFO|hv{UoX Z~xiCEUo"E ǩXIEE|w"̧nh\w~{>HzA 9D!8$%gGo,z" R}QosVI h!0{~i@ +I7k_k}'8'S*x!k )e #`yN捚Dv~]zX|'[B{\Ʒum=u,H{2aFA8.7 ^nuqcv :6 hC}A{5xӨg浏kk%WC\>j'*cô\`iVZcRu?xt|Z^Xyucgζf=|&=.lfELz#]?W#Շ -fEEc}^zxF' {;+UCKZHycj[}YeunIl. /?}y3&a˘LS3+zթ{jV6U޷^{66+p$2@<^.ֆCH?wdA(5\ӱp%֕/kn&8s]Qe`nQЖFD9*&]wa]ɷb=U]䓩qsl)c-x} k}j읁L H%0yvkٸcžNX! ʻnqhe5ˉ%n k[쭄j Q4v`i׃P{Ug?'sjRG q! FϳZ,PEvk |;ju~HԤ8w4?cY qѿKc]E,#}}"~=UCooVpw;mb^Eda D7|?7ժV֬՗R׆׸cq sbz:r2 A68)Bw3'YCnuS?ͺkJno[]+*3Zm׆ow}mشC L5DLD<]y fEfl`!~5lv4{X:]7l6 $!ӶX;z"=yqLJ't QN=u[صr2]-o p'.S~{Fn.l -c9lc7ShBnј~mNZG-c3K-uu_K΀8s}M=lh2@wonbFs*ĵ:=89}Jӡ;}K3#ecf1unn3gg5rgm6=N@v9Kk:ue52~~ks]U}W{/O<wsK?wZk71LpǸ51X_H{^֘:5<'oԱ{IO ~-I}, 90s>rRڙ.hc3vBXUjmeNkLOB /Y k1:Wk]cVN%?ZA]8=,9a5"~_ σLIKX2'n{TcrZ콜?]N{os3n,pF:3fX^l }[Uet+&YcA0ǰUGAvKKl˩ky,u_5Xq:&uj׹\`]v>{/w_ꮊΕR D=W&9;d?1}6X_`HnX,dAkeΖcZ+f5m{Ihpi%wgT-֏mbK_`md; lzD~_zmu̫[Mmkm veNs߽'zwcu;[kI9fI,ۤޝ{~/^uxufYn{? w)P3'-su˥[kj;{eGOMYﴵsZw辶d1wޭ6]K`dMGqqc}Fm6?sUTW[k֖kZ=֒$F dk lα7i`"L ncsAT}h*.8h2WXS-&gulq˛i`v֝,s+r}7"Λ[,'";UEZ`&Q{:&\gH~,My?XnN%%AݭsYߘѱhWvb4V1_113 ׌dzqa_kV{w7ZYl:ژ\dh8g˯3A=;ӥoi:]qSH׹sP[CԻcwߚwc}rX/ZzQ)"cM{`li". k\ ӌ쓻 *{3mg.6ݡ:[[Z;ߜ.kl}ĸFcE$08dAVI dntKyI| +3?MMFm, sKwH {{NX~MVmj-@$8[}]0O,S Ʈ_~uׯ~ື]CX[F/}}͐wVX$>YCwd쨴VCI{Ӻ=`1mYip H͏SsU4ՓQ_yԶ鹕k^hYO~ =s@0}'D9Ӫx:0Mw?M3}K_l.v,IHCf(_i2"ͺF5׾sckpsqwk{gO}6K{ @h@^?U|\^L9nd~w'>85WN~#V.}٢ZsƍIs}kݵKٺ+ ?ffS)2XfHvƲNeTk}kGƃm76WML<-n9έmgG 1!.!.#F#˱T\ M;m(Ueuc+=*Vۈw\Z涽qK*({_mk?MUHL_&2nƹ{A=I9H.Z@mmuժZ G;%pbnyws~K_#&_T9e^2ZN$5~#Ҫ]W \&\퍩N@;~r= 3F_}]nnhMjc̈́~ւ s#,'mlcv-7ȳZ̢|Wϡ'L54G1[?? $#yWTt=sjl, gso?I4FӔ,[}&D^W" ksCcsxp[[jV o7l;m7~Photoshop 3.08BIM%8BIMHH8BIM&?8BIM 8BIM8BIM 8BIM 8BIM' 8BIMH/fflff/ff2Z5-8BIMp8BIM@@8BIM8BIMG cool-ponynullboundsObjcRct1Top longLeftlongBtomlongRghtlongslicesVlLsObjcslicesliceIDlonggroupIDlongoriginenum ESliceOrigin autoGeneratedTypeenum ESliceTypeImg boundsObjcRct1Top longLeftlongBtomlongRghtlongurlTEXTnullTEXTMsgeTEXTaltTagTEXTcellTextIsHTMLboolcellTextTEXT horzAlignenumESliceHorzAligndefault vertAlignenumESliceVertAligndefault bgColorTypeenumESliceBGColorTypeNone topOutsetlong leftOutsetlong bottomOutsetlong rightOutsetlong8BIM8BIM8BIM _JFIFHH Adobe_CMAdobed            _"?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?ek5{ᯱx":,~="7UCm{ˁ]D,( 0;t[ m[%mm@[vC&q=?~ϺDzc`^;{no~k,z"gS-<zFO|hv{UoX Z~xiCEUo"E ǩXIEE|w"̧nh\w~{>HzA 9D!8$%gGo,z" R}QosVI h!0{~i@ +I7k_k}'8'S*x!k )e #`yN捚Dv~]zX|'[B{\Ʒum=u,H{2aFA8.7 ^nuqcv :6 hC}A{5xӨg浏kk%WC\>j'*cô\`iVZcRu?xt|Z^Xyucgζf=|&=.lfELz#]?W#Շ -fEEc}^zxF' {;+UCKZHycj[}YeunIl. /?}y3&a˘LS3+zթ{jV6U޷^{66+p$2@<^.ֆCH?wdA(5\ӱp%֕/kn&8s]Qe`nQЖFD9*&]wa]ɷb=U]䓩qsl)c-x} k}j읁L H%0yvkٸcžNX! ʻnqhe5ˉ%n k[쭄j Q4v`i׃P{Ug?'sjRG q! FϳZ,PEvk |;ju~HԤ8w4?cY qѿKc]E,#}}"~=UCooVpw;mb^Eda D7|?7ժV֬՗R׆׸cq sbz:r2 A68)Bw3'YCnuS?ͺkJno[]+*3Zm׆ow}mشC L5DLD<]y fEfl`!~5lv4{X:]7l6 $!ӶX;z"=yqLJ't QN=u[صr2]-o p'.S~{Fn.l -c9lc7ShBnј~mNZG-c3K-uu_K΀8s}M=lh2@wonbFs*ĵ:=89}Jӡ;}K3#ecf1unn3gg5rgm6=N@v9Kk:ue52~~ks]U}W{/O<wsK?wZk71LpǸ51X_H{^֘:5<'oԱ{IO ~-I}, 90s>rRڙ.hc3vBXUjmeNkLOB /Y k1:Wk]cVN%?ZA]8=,9a5"~_ σLIKX2'n{TcrZ콜?]N{os3n,pF:3fX^l }[Uet+&YcA0ǰUGAvKKl˩ky,u_5Xq:&uj׹\`]v>{/w_ꮊΕR D=W&9;d?1}6X_`HnX,dAkeΖcZ+f5m{Ihpi%wgT-֏mbK_`md; lzD~_zmu̫[Mmkm veNs߽'zwcu;[kI9fI,ۤޝ{~/^uxufYn{? w)P3'-su˥[kj;{eGOMYﴵsZw辶d1wޭ6]K`dMGqqc}Fm6?sUTW[k֖kZ=֒$F dk lα7i`"L ncsAT}h*.8h2WXS-&gulq˛i`v֝,s+r}7"Λ[,'";UEZ`&Q{:&\gH~,My?XnN%%AݭsYߘѱhWvb4V1_113 ׌dzqa_kV{w7ZYl:ژ\dh8g˯3A=;ӥoi:]qSH׹sP[CԻcwߚwc}rX/ZzQ)"cM{`li". k\ ӌ쓻 *{3mg.6ݡ:[[Z;ߜ.kl}ĸFcE$08dAVI dntKyI| +3?MMFm, sKwH {{NX~MVmj-@$8[}]0O,S Ʈ_~uׯ~ື]CX[F/}}͐wVX$>YCwd쨴VCI{Ӻ=`1mYip H͏SsU4ՓQ_yԶ鹕k^hYO~ =s@0}'D9Ӫx:0Mw?M3}K_l.v,IHCf(_i2"ͺF5׾sckpsqwk{gO}6K{ @h@^?U|\^L9nd~w'>85WN~#V.}٢ZsƍIs}kݵKٺ+ ?ffS)2XfHvƲNeTk}kGƃm76WML<-n9έmgG 1!.!.#F#˱T\ M;m(Ueuc+=*Vۈw\Z涽qK*({_mk?MUHL_&2nƹ{A=I9H.Z@mmuժZ G;%pbnyws~K_#&_T9e^2ZN$5~#Ҫ]W \&\퍩N@;~r= 3F_}]nnhMjc̈́~ւ s#,'mlcv-7ȳZ̢|Wϡ'L54G1[?? $#yWTt=sjl, gso?I4FӔ,[}&D^W" ksCcsxp[[jV o7l;m78BIM!UAdobe PhotoshopAdobe Photoshop 7.08BIMHhttp://ns.adobe.com/xap/1.0/ adobe:docid:photoshop:7a397ae3-d3de-11db-a8d6-aef8313a4abc !Adobed        "!1 A"2#340B$5CD%E6 !1A"2QaBRbqr# 3CSc0sѓ$@DT4dtP!1 AQ"0aq2BR@Pbr#`3CSps IxeNM4R&XQWp,3Nv3}H5l&O^'QE;WsCi'QRyF"! V`F$4 t=pAkP;pYʛ4uy&9lbchkjI"=`gHۀ g'/\SqrPAHt9:q%,<٤A1iE3Z&%w<*tJ`h$n4~P1XX-Fh3 ZpL΅f|QDM,VYIK1 <_VCxƌ* be)Sr!<i{t 굍ג{LZıd$On' ؟\ L^ [i`Pz < {gƟr %=ǩǃ"j2%TYJ y2hw|1 h"e%W1i7C~]A Y:Уih%I¥Y66Z`S"L5@EsGGGͯՄsڼh*m64@MNZı V5ZTT\" D8z9yM#@)z^o 7F8B,z+BEqS, %1tdnCpFh64h`K% 4}`ć`ʐFJ:̵Z Y0 K@.)0[o*jLIōB[p,Cia @k`ٽ:e+;fdq ^SP杓{> Ҟnm'BkJccOlܗ0~ns XRI\T07X;f^#/+]4i=U_fcS$cjlfcLR{ëܖ/yYi;=ݗٱbYi*|ēik{gWP|Vs72t3T^ƌ 8a>^0&Cuf?,'s)VV 4H &(5 JXeɳ STQscDe RL`SS"zↃ$YGG;!t)f 17j6, aͫ'd7hh=g|̎6 ܟ'n -BX5~\~3)GqLF(0>ܓپ ♪ ^(G~F𔃍'|.KcfCD`y hAR&;KDEj*E raO 43}Z$)栉"BcJ&[^x83:B?J*[CtrqZZ_`KV@'Z3D17GʁZKKiQ-8D,@rNո%3W̼DkkmrLz\3㍊Z כQ5t)z^6ERSr"-af@ @[ aYcLWM~/%;63S3ry`~4`L0Cc2\m9KEl0R= Yc=τ.w r7p![8an{!!7Vpۣ֗f0E|3 fPxT"xk!٥m.% +8 j=3 =㕒Đ9mnZ-U%ږИm#N!00DUޡjGW:^\`w}OV 1 {ڡT(-Q4Kƕ;ь X%Bf\i}g2~y!*0n``IALv7^CGC%mZ`&YKs+f!:4a1@oF na%,c- X&)f轂5>I*$%tIC:qI" \QG ^EUN{*[z@2Լe@ 4)TJC -g#Y ggc>ka+<3wC7,75>h 0lkCs|o,VnTc=3 hsh!bK ^x{nGE9\Mr2:i.2ud260/Ŭ3ɇbjRT%U2^e7s .KH1t'k ?Ac8ND֢+pMLu'F!`{[m,s{7F{-{E sowڍ3=aD^x$, A{x|77@}kh e{٩g*tD!ۉ'OZ'ۚ5-a>n0qަ0p -!w|'0s4 r|e 2`3SGтŹ2CЙ̃Eͷ/f Y|U9MLpFJz|ƖihTcɶvl6~up3l]*n']hYY*(r@ɃuwOξ]#56yL,fO99A&hxX)tQY/$>nKh}E, ^5:['s:{}懷=kBZu XA͚G<ŔSHp 0)\Z[F䲓>yX .S:JQ+M 40ƆtlzNAdk2Ҥ-ow~WZ_:)QwTѧ(pu/5"}H|:XU8zkW7:pQ'4.fKyTsmF We0Y4tk-0za, zwm1>!5bK42^cyKjGIC4W_9Zg;OVS Z2^{Ҟ~ȴrvAhH|>g&atIALGѨɌw/5I~wLn-n< ;S~ $Ac|쿟KRk3 IʟԶt:18U/O@ Id̪)p8+#(ܗTPد-0s1!gvՑ #-S)Z֥I7iz4J#*Imrw;Hj[=2곑'umO D徢z^G \f]=SSdqZ@sЁ^Vo1U[5ҧ45=mKi0G݋9 l2{Ёma谰? 4 3`'Y#=R"8t:JDyy~3c7DXsK1XXN&̇,A֋% <еP`R`Kj][{ |rUZRRR8@~ץ)T 4/BFSRAsqφ;q఺Uu([{@$LT>汁vbA=9:K|:*uO2W43nH,nT]so9aы.?ZX@e5 lbFId׬,|YHsIEkX!𗩪s,"~]bnf30Ƃ )LŔ?Er1kiS.۲:'0z"A3IH4PvB s`lFLSD =ͺ[<@iNX:ͻeZ9IQ-rX0w4n)6U./R\rɦfe{D{/OD0G*Eh<ˌ4Zi%.sWG-rtwKT6JGW:t$ TOnlS koOM5D9b\ě$d=KmŞtgJ>\)b9e[^Z sa"S4K(,deKa&'-[ZXc, bڪ|b//ҚSyⶲv*LP+1ƪyOwKbY*ihHI]Oi҂Iνe$\#z-YR_yKu81C5\NzMnvcUbDɥ@<)@@u{\NB>0-4rVS3A(}ibqlb}4 _5WG8TU!.A1>pֺNS 1`4 tzK\Ů;hǵH , {۱$uu4@ MUD&/W-I{[FbBs[Ss= V:稴j,F3=yRWTWRtuӭPoQHW妤J{9x.7+UA_WIfOt4f֖0Lv9:^9([cjrW WU_tM+-MjM[I3!5YC+<.gs> *32j1y`4?r F]^k݄Fhw(gPief ŷ ,Z^11=*j֩Sqe51DZcجD F/W.h0EE\мm"SNc?m YGa5⛮i4OvvX|ECףt40i֙x,Z 9mz*ϸ GEАgPo+1*T&<Ȣv:zm .gH7ʎ j~$WGQULJ֛g*2쿓zuMZIp#&tA.O>ՂSSlSg#%^IYIҘz&xH^Q@l2FEncQjRkr}6JZ`앃tUK%itbu~NBϵko#gj/uZ,SI7ӮoӼxij㥅M٫f^Mx}["Jb:mEأkLyjYɰ51b9絙暣)=hGL:y[wBPUϬ(A){[[TS>,#7:Q渣P2A5S3]rt(=ׅTž&x͵ h&nENgfϸ9܌|rJ6/DWW%$/vN̦(rX/Jj8/_CWW _)|-0i<z%۴ b\}΀r|ʌT`Xk'ph+YW+$>l Pl;^_cAA+QhE>mjWo"SYBⳎ~#⾗PDtI8tAzsi'XTS虠5C;5A5TG_%PWI6LA;XP%-蒀O4ՙ<d()[$ 0t`Ugm{#˧+G+潻^XP{OC_O F!K,zҚ+٭Kc%$yO&le}.mg?NԀx{|,aTԈ1dT*O(?r&vxM]MVJdњ !mG<` t(Apg(KZyYͺey)*2 HuF&IbsSH-zF4B-ObJ'ַ{ae?:| tIR+E/C.cDZ]'zyU+WOH3]] q/sj~I}IͦuL5PyGJM*@9o %H1UD|~),A$6jl`'T+\Ni>v*V.N{GU>!e\u4rh` EQ'YV K3jv"-a fj\t+6Hw' K`$IR ;~K㈗:.NJZC {_=A7hP*v{>r̖tg1;4 ;d4I5AL=lI]Jf7%0bo>֭øU\0GgݵrB 5l0 )NjfrLtG2pf0.*9<hU|n5Z3 ¢%TFwMԋ7/i~xZЦ,}C:U+T;[/njr=D $-䴗LD\|Ä{0hЮ{;M)CQ3?G^%vI$~Gl} iUMz[^K9+Fj˚ץ9z6J][0Ķ2:ύ^~Լf.Ϋ-hY0#4G%b#7CIj%[bHpm&L YE]Kϟ?%桒QlUJ<~_W{<履d Re0>GGg._ lß)5; ˩jB:k Uj#)^?7f7#X6+אrM>*lθ|V V:74rm=\w9NMǰZNee+7}yzǣO.P8>sI4/))7Q:/XGS'RQYk2#((A_Eo=) PV4)XI$16\osYNA,SwpfϘu~i^_Q?(uV(XOKk]*̜` *teyNgWgDgif>Q#,XBbjp;C57fꙦhnӈa};ĶL@eT.UG܊tgj<':kTKCjU]($ ^$P׻K=i``R p{oE;R][ LwD g3sds-T>5>K6J [|NQPpd􉹩i;XBˆl_ۧ/Ze0yC\Mf_xc-@a K):k]otcf75GR,34ɜn*^>CW狡0Bׅ‘/15ЃZ3s$.k5; `'m&{&>wG] ˪ZCSGV-@SNh\^-:,ه~jz[Sq)><(0< 55ZSZhFRTW_,KW'RDSi췶IsM;;_ ɯoj_>dm{*/S'fB-MҗW{<>w8_5r)]P {Hǭ| }:]M2$iR۹2tלs_̪7%2NbZVd]d_}m3JH\^zJ; ڟ礩#\mW봦&9ezSM}OG)cھ}VMzo/.5XzI+nlr^s죨[ons^jǦy$ ~}rO2CrcHhl툂-BU%+;͉$~_C)2@V6*}ӱ 7GV/ 栔~qj6wνlvƘs34BblLs:LSgpBi5;b6UUD+l~qދm?MaGr)a/ GFM= >usB̎G#%գ,k<zKТg0}#>;tޯɹzZ` DJI{qovPyMYڟKm |@ KAzrE1\:qWD]-M~؉$p 4d徎UW?&!)[Aڑ8fTKO1=YW}rr7/լ"{eݡ-u̞Vay9.Jp2CZWF7Q! N˰bR_toO98f2rP_sռ6<4mM| Jq҆!+G)`ov3Õ\t)Ӝ<ӃTe=Xyh]_N?%mu~Q7Ut[}\x]̳Ѧ5U΅6s3/RE35jeB\>|7yqWs;&sTW&tP^o cq-r;vN]~>6|+"P]^WO))}C ѣ_*[їچ\AT+cDܧ^0GJBYuuiZIn^'Q~wROGOsa3[?Y`sm䶹 nXR3f+_ rmҖη@3t77^AmtgtGwƷP?)4y])6 B^+K<~Y4Hjl̠QlQGh~s걅;ӌ+a15Ly:Τ5kFN$~^lfiǿ糏5QղiTObOC}^s}V ..gt洩M#ܺzkLwK[1ʜB:_3O]WnSKR[Ԗ+qOSy> uOCW?RqK-Do}u?>:b9e75[};O&ȯӟwM?]צVsX7ϯ?I^|֗=n'^M/羛8+%ƿr^+19yҒX*Je-ɝlél>'ܦ*sUyN"bؚA7\g)55Mͪ+vmI6ܽI=LvngD>J5iyhh=q;<H>tguj#N!fVȾ.[O?m'W/Һ3rK|{f|Ssބwyݜ*f\zV˙쒸"+A.|8-Pp>:{.V) 9ʴ63|ɨ'3|{:ݲt7AOiHvL*]kzg0 Cn;d]3Qg ]*4e?c vJKx2vMT+=@ln;bΚ&sl&D_S֝ T:ı5o/BMvhֵѷteg?;< bPՌ}MDS3Gh=*Cm>;/+Ʉ)yfcTlfyN:uM9Ѫz?My3Wz>?/ϭn}\#bui91J-ӛJ(6.P*6©TRNj!WҘ'C YS/Úܤyx) qsLayE6]ӷ(4oGs{'K͋ {zKe2L@rNA|ZC\ߠz|o?e1_}/YICdSR.?n*S9IG2kűg=˷Eq@nr/۪GS>KW?X&(:K\sU_>|cM-K>w!0e%;J*RZ!ӤSCqҬbi}ԕ7IӪF(O Tne3*D.m"J5&l3Jn2Srgr)Vt3'૲RQS: S}{5U'usm|ͧO>'{|>B9ފIrRfy0}>7~~2{]Yww\O_m[ѧA f.ĺ[QcfR.W7供櫧u#GSג踱 +?ñY˵3\XOӉjOVBz 5+# Y3 .@tHDÕ:4;AYeRÒ"GSICBgy!7GzP[s\>9W*MXQ^ZC=tΖ]9I,bfa}Hq[(nWDw'JG> 9SO|BwR]5_Vt:v4ס}MpӍYհē&e['YU0Lr_s/X0U8T.k˫͎ar7o}z"Xg6r[G$ҔEKpzg-Ҩy&.>źNJ:+u۝|"f@m.GJ<_79O?w/\ΖUuf s͡L#Ts$ T>^ה,ʥԉ ?"1RX)CQGQ4$J09Q{UQ;oׅ$@3] 8 O@yyn!@mhݤVJY/ɦt]A$gaoen|e\vÕ.TvUEJNϘ]k䷝u9~]M},^SC?@}i-&I"f%N'P-Q0V/L]UѫF8Gd]L}EB=J)u:ytQ=UmyI% |(llEӕ_=kNwdit5A(-t[T!’_o ؾfV*Ft@:ͩ]٧P-ZT[ɬ&}g?5dǺt;q|vu*^/=ϯUGf/z,95 J11ǕQ߼Թ}.UιSp y!F>w̪a%G] hb$fFg~UVʟ5Ce*Ytm}qK:CL=p1{WǷߍyQ}ۦ6ecJW詂\~Va,:71J =FPXdc; :+'TZM<-km/JIW&%ݼ/B-"Wns9]rgT|&g;%1\uy%[KYJ A[o79u9I\{Cηoݚa"+^E{c;:#:OpVS3/ ׫P~m)WK}jG3'\XHI^G\ܭkӋէ zus\yNBfZSOf)5Y F 9QӞi63*gEkz4V/Jdy:Xlnm:Njo:([> l)Jej~Iy}kw&[8jC)E\uЫen*dX1.r zXJNQS%֑s]7d#h'y}܉O$s%aoSDikJ3[ 6>Zz^V0e< iCU\߫^M[hs cy/^kU:sm)TF:lbKuq5]4RqХë!$u9g5snJ mrP^M;Д:jA۹tF}y2tx_gS{d!TNA($E3zz:9ϵbn ͎o7#Zră+姥slѹ.al):O@s2ym6(z/rfx_;r~Z|ƫJ\Ⱥ1N%).ϖK82t:}N&.Qg+,5%l4%m7sg+VN4JY%i-T`jVj73wS рI=UqAӴ;34̴ca%ƥtE^\Q%LZimU<AB}7: [[5 2QEe홱Slstl%d 2oR-QMPNrf73IⶾOR5-%~~/6:IۮiUjUhvnt_{E+d4G2)bK2/sz6td>s6sb*wo`Cֳc΃Em{$=9:RAGAf=vYtbi6x 02PܻIczMr/KE5+%]JBWcb3[tF/j( -KMMRsݚ}2Q4")zSU/VLL_ȸ{rnHPәͩ>uZ12hIRږB\Du3GHSmJʗ&O^W~JeH`mkߤgLP|Og4_U(tbnQ?M{R73~oK|<}G &VI4[5n6WԵi3tY P֭ `G3Eff?[6I+G aI6NmO=`T^ӈߙәΓ%DBrmOFfhotl::*L*%qђ]8:rp+r K`za o\XSd DAiкkEE]U!])uIX@:GHkY:}7OKdWa &/k>m\9 Lj)ƟUApbʞZ,MKFMF^!lY% ]9SU*k?]nM)1mbkRA>,J۠ UyG'J)mW˯^jZ61b2iИOE7 a;viIt\.݌ɵ[J^D#^ 17 SZ,PL֡$bY.:&FΈ9]iFdfY;s-W/Ks!GsuzSz=A"F!\-g&*\{㱅 |jY Ґ}};R9 WDTfE&/4[ YL)g2R sȧ$Nt4fjfEzYV9]DvdEOtj>܊|=M̀UKHdZnY fڗ>TG-@ZLuɵDbdb%.ǽk,79:}~_ߞZ44~_׭w;9%Goʽg%?L~"?R/fY~W>g3}??%}kT|Fξ?+*Ws ^H0gBaHs1D8Ff:B@3F<~E[؁VT@1#L 0L8L"` ΓD퉜B>h0`-hZn;d!rcҵ#_$ , q; fd L]Re3Bƪ2L"bu('^yPgH1x s0`B&`Qr[ 1B"V!Yb*~2>>483D< fq'3NE`@f`ӱ&v㲙1:1 :B FeFB' 30ɘ \a|bgYB 333 9&}'0 hԂ) ) 3X0##=@H;Hf#㩁cEt 111:N :γ:;B~3&ax ?bOiLB0 !\\C®Mc&~sam΅aXT L !Pf "v3?`&333 B,+:`B#yĩK3+-{G:83>%I>bF~qpIQr>3s3| ?LB'Y`01 ȃD00f& (O@`d +&b ^>2f2BA >s 0LщLA A 3)G~3`$El bC!も F8<ٜBb1"9C|CHO*%3,kFf%p(Tf~&fsCFN~ L"}LiR#LUI1Kp>b 1G `!+$|`TB`N#0`S:Љ'a8X+39L'h0Cr!19'1 ~3Y LF9?k `?}~>]s 9 ΰGpAN3%b;ZFJ! 3 v=k'1~3_6`D^h b'hNf`G`C &b?F{@Ӱa"v1Y;tA/d",o+|rZJAfx#b0> 3f10 @11`#9I&s#x D Nǰ+P0L|0AɃ B>Ffbq9A1al@I9b[x+,A8Y]&ƒ13$P Ĺ-0bf@!oD&~3?x1NtX` n䨟Qq ̴F2U_A3$c1`ԘH,&&bg¤LNu@>1 ?Qf!r.,"s I pL!\9؋[0p>lWa)A,8CE(JlG\΂f>?q?~ LO`L@8لBWco&>Ɂ@ &s!3D Σ f‘~0Bq`0u?$ E<DlG Ru".c339fs QrL0g=fyN`-g\8N N#4$'lf?A?g`sžc.FÚ'q qo!vw xfyfsQNbFWL4*!B !f4k?_3C3[ 0|}¸c3OKXwmfJ߽eV)b 8db 3 b   *j]"U /.|E#4~> 0~Xf>sDE`xTr#lr2 S'bVe:UHg4ȅBfTz `30(lV;忑_a&~u̲"&5+s/CcVR^@lD^cS E0԰x\;-LUC/p݃6JT,.BԆ{%;`"Vʤ*x~`"h 2Al=@$N` z;5:.RN{,*ֽcγ]Yj;)*ʅ?h3}tzl쥖?'?|??"XEPap'Xܓ2 $ y2¯0YaLÑ+9p ǜ:p݁*j{ѺR% X,վ}A80|~ߠD(!NM-5u "ع,JuPq6r ?0%bx*[@sYTYَ ?KWb }{#,vяba?|L* :CNӪ&2p0q2 5s-*pUd`~wl#e`Cr6o#67k۹n23C?0 DlĈ[[tZ Nf1*_r;pDV%U1NfXT UXkc1rA0aL;ȇL|'0 O'c?q WMErʷkE4Z+8$1m^B" +pG`Z{^Օ ,H$ #VxL'L3CA3 Zs[|"ڟ-~9'dXbp! 0hRaVGL\%JzJV~V K,T +M8K@n_!Bd!~?F?PP]V2"1 s W ܂Pa`pST; B kYڻ,[k z[s=lTZĵfJ.Nb $טGL~,z*+H8fgfs ٦kFbore6YAa*Qd֊%b`%V_mUWj/;">׳`f# &!031"2Ald*`8`}@ffaFLQCVLe*Bc;íUPTu Deb#Z"blkhUZI "]H=:!'b7b?@0@q B33cX^)n?X?Obs30q` YJRv)Bq+GbPaffg t,)Ԫ%Q~ŴQNBưfș~^f].b8}L*A˃ LLVab!|E)K`C7T c`? mUu) hG[DT@6ֆ_#3`&"9bNP9 2!5YXp >:v@f Xfa D` ]ʔ~A'ִܳ(EN! K6WYLK"T"¨ZzeZ+NǢd.ga̕ `[muChݗG0iۖff’5yehO˶ ¬YRp+dC -uRsY "dN=\X?'Xr'r"v_)wJ A)ِT 1p 5`9agM]M ep>q 1 5[UaYC*~3.?MB ɘ*R!œWZ57WRVxw+A +V `  rFV-*Qkv+6v=,*@Vf:V$BD$'v-س* ek96bVfX@Z6>-QaXT-J`l|˶Mlĭ"°ʹ ZتÁٙP^kwZVzZdRc2WUml[f &RѦȑ ̘&tqI Djx $1!MUMv3 l–ώ2 K5\RKeɌB#a0?A J.ZQJTOeZܮw$L;@ anuq ảՇҥK@o[ύ\I1d m˚RYHJ[RHe]A Z!v噘f{=a* 2b ["5m /%{H6͝+0sĸVA R7NN$agSX0|.I=FcZ]% >3t53ݏ]\U0JB+c6DF!lÜ(Mty)9$,תB*r==-~[jKA%2r 䊉T`?`rcb`3%?eV=eTYJUE=I/uC",\O~XRtun#6Mk&8#9fo}+s6'`;0 `ʘ8,9 U"ƗjMR떯Qf͞*uRD۽i]}̈́eG` c *ˊj4ުMT9_TDlL] [KNK#1Ħ,#F-aܢF3$eIw4&@ga$=S, j`9lU72K3}#-( \XW7"5ڍٙGTJjV Jܘ 4. LQUF $ cSrm"aY*b+rpVKp u1W$azM[`C: R9^$ ]k` 9,@q)denӰ`(ud쁼cd0LΌba-F1rW X+ ! H0Ye@R"SF+ًVI-^!63mJ**Q]tjZ؉}f׬ֵ(c^SQ]xA a.F!1g 3L 2qPU XXX9ec: 劌:QH*q<8WsT T=U\\**%IT*h)L.bv 66"N"e* u 78M_jV=JD-ʕZ0@@G4tQ1P8Yg.&-F26dƷeÒȦc%9P`L񌹌jIK"Mzf!Hj&C.~npVr4]R *X,l Zٜ6]ݓ)tCl?ɈȰ硭j ùW0lb:gE<8;A^}l 381*3L$fp`Hu9bT'U3F{88%SQ=F e1l8׶c,-E$g9UE¸ߡUϯEt#Tu{Jk?H 'h-é'8l'I#OzrbUc IfʄakboW\`}0R#'<`/n|"D f'c1Nxf *@% D`̪*,z2KCN;  gpa8KE-( THbNH,Ls`+8<`G%c1b~p*#G!Ĝ0-P>r\@ :13eI=tf@،yⓕ# FpO1! X8F#+ ?֍ZL%#f'$$I10%j q*P\T S_Ҩyf#9U9X$fXI?L8ayj; xR`\½a X< 0ꅰa_3d̜Dezf`qܞu x2 aHA,G9ruXˁNLd`(e7\A"1\?l 0aac0YX̵  Aͭ}OYN8Z #7Nz"u $efK8 3dBѬTeF)!2a"%`6 r}~A~}+'FWoo_o"WOMk/)?KBҿ7/}$2ߪ;B0L` 33L C1!?4-;N4;NӼ/;N? &3 gi -3!830 33;N!33q33?hL 3Lbd338Пq33  ̩8g0g&!"&csəf 1Cш>asNb CN%| :|xrq&c33`40~"b>1b:  334c fd:*`XUpՌџ b}|# ߴ &V}|b b?0>'xZ33  !?f F&~L+?ȀA }Cr&f xfHBa^"$O~XL`\|}L ]я~IN&I=D'&DщLLLLA?@?~RcqvPΜu I0LT # @4d³?GFf@fG3& &11t Uc;N13j >\Pa\|D? !1?N!&d!$A>hO&&&%5dHC:f5dL~OB?LB <9f`3331FJ. ^N, er`3Hӏ+Տ~ |cfgf ,0Aʹ9f13eD,'3ag 33-;Bٙ LL~HQOd?7\EaY&D'x` j!OӴ Ba2u8a\C3K>3;߭qܥrB`#0O ?q`|"q0a#0\|21!31[C;L9 bbNuk11 >ϢB88B`N * e/R& l& Rf!PDˆHgx.11*\O13<?A@1G"f>b l@И LLWOvq1~9ϸ0VLT >W0 paa;N0O`0C?aA>?:c d~13333333̯0ۏ0#D1L2k00?sE 32'331 B1 :ʀe`3 bbb3;?|}> 4#buNן&'"¶AQpA1 f?A932?Y> e$ Wj]@8 e LO - xVIq9??dF9,13)nLGhg|"v-:ĄFhr  0XKµXR>s3 P}P:4?f1 "u& i?ocIⳆ9| }0F~"[09X rR!Wt+ &c?0ZнSReGjI?ÌО`evX5G%-W@'LbdL31F>O! 0d&J0]Ԯ?@߫T*+ijG٘%L`Tlg@Eg~fT6?#}q>!?~?ypG U?0~Ȅ|YN!9'Hv,#OkHD#`mFR8g0a ™u=g\"`v?dbff!9D R2]3dg Lp,kkL ?ٟs?L~"b0Z` C 3|10I0r;ɅuA0q,o`#s~fA@23uiաVI-&30c isXLFqDc">333 080|> a7gɇ1f,07Y]Fb2 >u C\ 3[?k `A03L<|~ 9QO2J;O?e*k2ۯMbȖûv^!8>g͌X B81q)X1I?N!3<Dvf#809}OC9C͙GI-yU+>l8{69A gSvY3; 2!b'pg3 1un?sp0C c0|LAN)UGpqz$kb`/j'"'`O fQs?xu e?y?=prx**V8,ÜET$dj0 Lj9qd |>-<6GW~1 ΘQ"xrD1C 9F ?% 9({V{f*>͕Tpοq lf6R nßbLfLq1 f?F{;I9LAB~ b"f-,%AqQ5REʴ}L,g3bf 1 VIbag0ǒN|f ȀEaՄ9,a@sj}Йsr``99*Ğ|VT,,c01*]Q~^I&~ҡWw`brIE 88 s LfĭL8$O8bq*G~@&XlB#qP!"+$$a 듃?mP#T˘8b 'Dv,V \ f L~!$sRs0r .ey*Z q;L~&a@`v3 jee'}40T 1'Gɂ ?a3& N8g+pHv%^!?"!wfQ*ЉS;~LZ09S5cLFpb0|J&~Ba` C3$@LqgpF'&bq9dA*gi/w3 9?+ k jU[A A9|NISSab,8kpKqV0!@&JIq (?c1T A90,'(LTss0."k68<~XOc!`˵!SZz+*b C~AkpCkU(%9'C19rL9 "C1:L lr "7$yA3L?cy$O0ps3h~3 =pB1^b3!<&QeŊU AX~Zc%H9HP`` 9'c'q3q31~DϿg? &s <8?gC?bNŇgL|`iHvX9a0 L@q? Gbg1OFa<@'90q8q1<1Ccb3,| L> ~0Br'^'#x1  '&c.2~]`LC0P~`0pO0| 0gx~$Nq; |sn3:!n}#&d > I99?or s/_M LAO0a/@'Al'b`A1 HO$Sa?kа|~}fբH~+hCIo>?h>~a1H,!xm|ـ+b๊gnd}L̈[0W3?odpCB@3x6 lȟ`BG"v df3lf3dLbp1b/"1#$3H.{< L[`1^va;@Ӵg3< 3 ByrDL rf-gh;ff; 06'w6Aa L38᳔y1 K)ƍ/F&<& $Ʌh;+3B,Dgcg3$c1"w-;LN&aiiXBx`xfaɌ& 0z!a 3;&LolwV!7 2'D03&a&` N" Nف &u2BNL :\EYb&1QlB 01WD)q' *c6J7Xgxfvf, M[Hf UPF[PÂLv hNCACxbc1tU,P 8a}`RgI#Y!XW0, b89_lN9s0°f`gh',0یvsN%Y,Ƭt'\N $syg b cY>S^:p`@`|3w  HhL'1bLf&5C'=f9c15oy_6)5 zRM7GE6PR~:[SlO\] :$rs ? F<;EaÎ u :S!@!\XANθLC1VgXl̙giw^#И9$}N!1L * b8".0q2ZA`0'qfa9&y/ cr@D6maǖ %on;q1OQsNQL*pֹ k[ֱnG=@ CJ`E9:\iԟl}lv]F֠%׬X>B,AAD 8c3 EHaZ20 h[ 'o8a0gHW0$"`1W'" CHq>@a"B2;ppgp!GF)`001b3"$LɄ#@!qC|(13?`9>#טiKVt!@zU@c`1>?2j*9 #lZkVyhC+Gm|saRaf ?ij?yc;daBy 1 lFxO")0d BӴbƯ1 pW3LjO!wlњ+b-ˡv'nK9ʌrYD$u0 E3<&2|v"3N0ؖYrM|3ȣW׃33 y!``"= cO_Tb*;1 emt:~x֠]#l^KjCrJݦa9F? AV0f'x%r,$lNGnĘpb*1 $?"#f-dLH `|9fttP@@3R BhsNEf9UH )3(b(-%2EX>`. rH9UXA:'3?gALg?0rDSvbPdz?z,*d}tkYV3tV6 rNtvci#8&..,'`c@D"}ÈG<}qx` _#&L27q&ٖΦ@ bB"gnP9$ 1|8Udu3 *210Rr+850&`Q0@R 0)\™D#at"T`>#N a|Ӵ 'a \@g0XF" `& 2\HgZVŠZ5WPcJ&[=pZ`bZ7Ym,KqAhB2Ods F :4U&: F" d3yQ u9'HPœ0d.0C>x&vg'R*+9#4+L42 @@ b!`Oc03jgnHq; p 0H @E$IH%.!DTZ}' 8WjjFX6^ nvm"`l̙#- eD--55Q<- ALU a'!`BgHDe" a9s r:°Xb׈$ \0` ObbbN Nћ <3f {@`.gn{N'a،󸁳c*Dl@ Fb8o*pSS:Nzb<#=9)=`Ozjڗ緯~mo02m]eKER;HՔd:8KCsE.O^_ _uq-C14 bϨT2W /`O$NaXL| u'S|}x@123㙈Lh3g*@`fsB3,`lNмO ǒc-wۇ$ι3kS<9293`eq8{PO-=ukR+v#= A&H؍{bV Mf*S7Vd{ V/aXKkt+L髏d5eO0VS>#}7B>&@U &!0Lb&Da(2q:")Q:F8cF~MfAdmaxl"5se2FHBB&De*0@" hA 2 .a{+l~yk+j8V1|7k+gmj{]B DFM+` `rƹmr+غ]/]_r) 8h$a4̈9>3܃ Bs_B 0Aq0Z1c;D`n;NӸ~"={GcLbpY풼 r=l"+@l@x0S 8"VNvYO-66+RXTv.e=e~sڠ ]&U.L=l ra^Q .7mHT"=RǨ%h2ВO0 `bu30Y&D``Da Vba}C 7 0;B0>ffىn;fffwx8,"T\sc"}! ̈0Ӽs>O& ~3Bb;pZ/Qm=^/a^^^18@lQp%\[Q&d!5Z{ Ƴ`ظE vig]5 a&ɦ_@PNT`} 05bγ 1#!qT@ !0y؅ Fiw9f! H`49d -.`sbfghh 98tS(LNJ3j|`\-ʜi оCbh;YU5=ME`;*E:5vQ+v8:-~zؠ*5MPC[bVW#3Ci5 YPZ(` 8lB3?u`'ab&q ;yv$a 3 Ӂ񉈼La?fhǓ &qanWIb I\^f#}׆Xec` 0c 8UQ$2Zc: 'h)h`M=ͭkze:#0$ ;g d56V6t^,m5AzFmHMDeʻ`aC(.z>[Zv[d+vζ"4 q~Fb`3$F"ggnT Da>89033=L:bb>13c>N[/ L&yL\C?I 1"*!E qZ`yF 63 bp3(sfu"V.I .F c;r~1jt_[K.{%MaKl*kXՀ.K:~nn1/W*alM7ʐuWjmJIk OFYK:l6,"yHs Nf&"f2uL ,,8NuL`^:LLL@ S1"3 |q33&ffLs 3:AaX2Be X G@ Ac̏ CN v" u0W ns8v`4{2Tb^T: 5f!laɋbV(T`*^;"^Z>0Z\D6viu R ȸg$`A|86)g3iwQhC{AMEQ!<3?3yLgns c sghI3<3iL9fg8o9X) +gYXV2ΑPΐ$ C!V^CQWTYؠ0g|/)dF(Fr#"u9$3;Z `xZWصTfT(mjj -P^h]XڃTl-Ye `zc;: o%@\D#,Ȗ͘ᄢjZc6 TX1ɌWl@2BFfa03&fyəps3+:  x??pzLN@Dc~q p c> `"1q / S10 ʅnj֧=0-̮ؖŰĶ Df$ Hu%1 fRnKمcccM=ݮ]yugQ5+d.RW S jzT4}U]Bp]pFEljVr=l|t0Lc3'?a>110&xјLf>9 lF?f!Yf }12`XLӌb," f\[z`$V V5g[1;fv0C9 4م&؅iK- ;'fkYEpvm6Suެy=ovWrPl:f*)\G^bgkSk)`Zli]>Z(rԱ-ՇdYeHˇZXGlN#9T70ά 3&ffg3;L32~33O}?r&gh[' 'Y f1b֤u\#LG(e)ϏFb82L & 9PHqʈ>$dE0f`32\C)IV!H'SnDpD1fUNX-jܪ.ɗ33kU,vWVo*]zMҷ_j=7qYWc+jKدz=v뺦6:meZnhm}Q])SO`mP?0W޿g jQUJ`Vд˭a 'u\cAB@>1>& "10pFxqN !)N ΰ,&>0&!"gY8l7n9p`Q0#00Hq Ů݋nOGa[2{[Vˬ-U76azl4٢+h@Ee:݇d=7!$X +4 g?B`X,A9ɜ&'33L&gf~3 L D1@'"v&3>TX8?Kketx'X9c !5 1D/gcĴk`98J@.ƧJb*͂vK^Jf e+3UYVZZ]Yۻa+DBe~Zv[rٯCam]ի .֮j:VnMgԵmBȻQVE,:eU|; @l}5v[֗~R6x6鴾T<-q)a3B@&&3!ԙ`~S@'X0)!1gfq;Nw a(^`"[O}?^D:/t`q1G ^:q&c~9gG1H"fѓN0g!'nG! $@cF92 g+P0@ ApvU*#gQ<`NLs by zۭo W\H/-`dZʦͶڶjN=9 G`vICq,R^m"S.VkK55S}?%t]p]V2װCaYX/mc`sR/`iFQigmm !/Zw[]zV,!Ñ]VP5s53ծK-+p˳XCk]z`,rF"`KiL(5zjgr!32 'f;d<4&v BnK3 B\30C1"ɋHTք'fBglc<30Difbg0LpX38}*KlYFuX^,ڈO}O#._[?TٱpZj[꥔jױC.GYlX|wR.^U|0`5on!0;2pԆ&1nڈl+FT>3rU<^u[q*@HMjZf9P{;L̈ 3;Ӵg<6Eܭp'yBNa0b)GW " xg\EYS0zL94214aʈ+>DL̘h\e\L1 vRb'f#bf CY&a5Z }OH D"H?Fg!`@8+bvD,1v4K1,"5LY=[6xѭZj/j͗DT,݂Vխw^.xwa#j6Ar=ZrأFZj.f$5QFjUV=u۽ttB˴˝L Qw%n(ouKIkls.[}m3kDJkE@łE;ʮ[Oe5 zKC3[c2bff!q:΂ Ycsat"R2|lc̑@ R bb2f;B`<c Ra\a~b.xb!lN=yۆyv#l`53͉*ˆڣgb^{j,v.<u*Nê`!pYO-?F \7n'_ZZڿ#5N am[;ieu8oh=Seޯ%iguMS-;$Xraό2-R5Yn5U5Q-%G`Qe)bSf$˜ø0o԰*pR @>8: ~" .*- S":1șYYrTp| ,V\N[k8`,Znu_^݁a w!2 3u 2cfb rKX-A=!+V|zvuج]{(ѿSky)_bĶ 5LejؖWbmV-^^Xtw݌+) ވQWՏ5Ǵ:ɑmN݂k 鲁K+VՒۡ"v4B'_e:jn@ cr-Œ[ żA~"ٙ1,rf l&@@PIx^sLDiD1A X\Xt"sY9JWlW ZfsX5j \5ZM+- }vd[ܮY1+B]&v+6I g 2 d5vI%TbsqV)mk_v-ڛZ[kG|OEV(ӭ%KK+jpmAv Vnkenη!WA igkVCSYK p2mm}JJ,]kfr[wYچ k]JkBZI rDr" n͆"`I d0(b;DhI!`"vCb-<*X!8Ż0܁p,` 0["x嶽qU}[cCIpBO[k5e5d9ۂ`J c_;f%92(-z6X닭E:{WE E(Y^VzdeQcʻZ~:,+U}^D[vTk\-+r]*U^¯׳oMMuڪEl˩B4cBV.Vof k$i-U6+%zG"ef&+!ȱIB@\C,cI@gy/ɁN1V# Lfv繀g67S ?( Y<F k!^t3Ln"0g:°H[YWծqIM6clQjkl;֋T9=,k {GYny@&;!T +[EFV{ z,9K!b+ M[h! 5_V3bWt[5zBln 5l=;=6+*[u{.\V o{Րc]ըart.h?5XYJ+5zԏ-Gt}vXv\[`(|Ά_"Gbr=^I Xʦ#'\α$VPBҺɝ00,\XPaIμfq 0&"6{*~[jSaW7ҷ2ɬZ7/6VMuǬuө5uowjP6.ۖV/^߸[^˲5AM (DK-+s2 gdpILXvjQeb0"/vAA1`L -/[*!#e[J6i^oծgjuj۪lИbG(PܚnV,B`CPAڟb(k`Vt[zoUrK):ֵk+B:eZ ]0 ӤԸgq[plWk&5|qP!H5X Pwӻ^UWjb,_Q0Z槴!JF̥r)pâk]gN!ɨlKm@ծ `ۦڜXf6mpFq=f6` cyR60TA$3)pbKjJwq^k%HgDRq, K<};V*|)VTk , jaCؕ [! QOV̡(IS2 @hFM.BOuV٪g쨸ȫe;7Vϱj1 x%>n5tETLK ԓ,wo 뫦Y@*U@++ώ3+Spm}UZb+VK%/Wg`o*&lm-57>+iwA鵕įV:KP{P5;%=]Dr[)])h|ۺ{GSk0IE4ڴ)ϝ)W{!GWJempnNJHe^%]u-B>& .IS*Pڟ͒*rLB&D'0`0!$dgbNK-VjUjV@TQVLeb7> _gU]x`Sf;Uhov>XԡP0O=,0ͩzݲ;ҋYЈ hH[PQvNYr|[L:@cei_PemCi^LqRuaKYc٧:[mjBAGe[rkkW![ DO+X)TN*u5 ثꘂҒK+ fi,5[]{:=nXЂ H3l+ @iՖ&:0G[PX꿐U Tٰv%(…{̘&&e*<{Tb_X08Xl5+^c+ǣ"/ce`1 ,1 2,;ŴAYFXM"5v'[V˜OhΙP(ԅkbႬXlYj:{6ϳcGnDY9ZΕVLmj଴dΝPUJjٮi}I+ORGMe3A噬YJ`Q`yReh|O@5 씵6l_nިZ\)bYs{4HjxUV66 Uzĺ8}j:Ol׊ޫ]f}{зV1A2XL%@+L#[yb=yVKe j*5ۀ<%2u+?(hˆFZ0E`M,2[ /obldv{v,h,[0q$2EEtb\@ICS,5kf! \Ak 'h0lz-q` +^!r/%8+XɬaP^# #^yK6x`Č1 Φ ói)Iæ˷bzk hV +³]C M&^ly,M6"\Hצq|ubسwuZ[bʬ:Cc/TƢjnlXڡn%vS[X^.[gg*e2*q l[8l4۩:Oa]J8[הnWl0[q][ ah0? qRvbkCdYaNV6ۃc_\g&+U0٭j`2Fqy|Ɉ@A@3V$hbޱb;RX cXHzvH~f{ -0#Œ,Vv;+/3`" ʭ'v'\2r-ȉӱCo}"}b.m )p ŭ4]HWKJ c` PSgV^=Vxw4CF%$5cd+G5>Ժni^݆PzR?Yu:_Ks߮0W&d-c? ,a(ꥅvG0[NWOq56@3RPZpFLVRM~,=o_vZk[cTcI-IY{@^|Fǰ4atFݻ~ IeְWtHB!{N ZԖQѫS:0I*,^ Ē2T`"(8U-ֹѭ֠cM}+T)ն0WGbh$W&R @+v5lG<m ZXY}phi*>͆ ,1g cVymhzmاV۽{{ԱRVX ^w3Y.IG"[1agD+rAfudwfk5. /j5+ww`!M! θ׸Z,ڑߪ&[ZNUת]soy!B(#6z̶~4/E袥\XueSb[l#;Xac0@oHlZSUiUZ(x V v1zZە4W X2_M>ȌE' w F~YB]H u6(!+kV_Ef&- U%w{=&'d`oYYvc:yoAҌɧ@h k kX <} 1,Kv8 1,pG,ǸVs5* tkjjigf:_Nh5 x*5RRD~^#aF[%l$ Vil[, ^rKll4H  Uja/zz/Oej]Ufʳ/jR 8[IT^Z幗+PyU{5Z`)h:fT;^BWg(hQ6o oܧWhUꢽ܍fsaeģ)- n]{VkFlV6 M{:?e(ޫYU*S8]0ܬ++(,; %a>G\Oʴ۴72Ss[{obX2ֶ,GT6o.ߛgU6;F$"bJVRmmf'^~ef5V${ d\di{+pY_ K$^57xڝu,REYWSeʡ&i)X$5Vk7=PU쭯󽬕3P(J PUv{d k2=MpK+^`C]$B!aJ )T, }iD,fĵAո6^o:3A/pM,4kWڭlo~Hn 3bVWSh[l_Mkl d@Uj(GlXPᘱdEucXFV+`Y1[a5mR wbH^n}->Djռ7 tKװT06A_)UE맏jZ7 *UV,L7E ' l )jEkE+޴QS.pd)u-`U]`T;VEe*+%i4+#՘e"MVLYs!񣅮Fɲ%Vg]C(T7FKPv٬5MΪmh)AjʂSް HFt+H^5*% PK 1#T1UհD;"v`/iy Z+-2XV?=#!"? $+\w5zUuNtvB^mb,VjСl6&'V_h}ZMV.ݷiFSR _KXЎЃt޳f%vy+m#ZªY}4a@!U ʼnr-B]jY+᧠W^˃-ZCl[N-^=Ԡ7Œ[ FqӾGBFem/uBuTEPzEfe촲YcPXZ,Z^ T\A15,_4M^YpUJ `e,K4Om'_bl}D#& .ս>'e{,KHɯ5YX,i+]-=&PzڡVAmtziz-QbYYײݛp:v (w,."XaʩA.)[)fEF8AeJ&jGB즴`,Aޛ[e(v6ƿǞ߶uTjV)CvZ cS}o7]4JV=\1Uvv~b^RMn/VҤ=ujؔÓ)W{WDulcqckѳmIm꿒2)J!iW*Usf?=*&WZ6ƚw)v/HQf<>2١ԪqToj@-|KQ}@+w7jmujmj\[/K=fQ]ã&[nIͱCn~>'QY%aLK*R]FPJ9>Z-nu Ekh؞R&AcP6QPllbZيw5w ZZ@E-QUZ/ *Z%6Z2w?6B mլq!b*iG_r C*l)ڽm4Z,Fjv\P(MOX:w|]1n"Zm>ِRmVMxzDZo7*ﯿu4Fcjk-fQm0cOeܰYaS D6k%oS]m@uu6.׵YqU^"52coZݴ(V-LmzNVa-}׶أOR0jG䩔=d}ﲷ*3] ߵ^o6{: *ZT4k 8`CdqRٻA{EVVڲ]YWb-soV8BO";{/ZlMkA0W޺O\ܾ:mVZvFMQ]4Et.[OF뎦&۪T *l+VKbi}Ԁ2^TUk*׾66Ŀ\Khafe}c,& WZmw%ѫc5eimXo}jWYLҚz[ jQl.w gMmK{]c׭,6Uӡ{Q!޴USUD[EZ ƨ%wi5j;X4}V =)nŖ +QtXjc{cr6llPjhݺRvܻ>MmY5媻ԭ>#qWV/NEB{w_ũUװ\Pi,؛?7ccwͰ USUnmV9Vr5wkͶ.LBzw+).ٌӵk{W3vuS5^dz5}]+w{R%^UKJmZ=~2lkg^Tzݒzb;,6mS`(_^%U^]nMDōbUxRY{x-%YF}}mvVm]MQS'l6h6{UURtc{svkVv(ȪGfk*§Ficf!ϰqnfɓRZ׳u*Ȭ); jc`%I9"P\ݴ5қ3gVf-T]ڻe] @Rב`-uX5{uqUTcZ4zuo]ԵJJtY[XRt]u몼[ 7)5Z0-ݨ.*ܳ?T#ĥ^)/S5Sm]}=nQm(ձUHeֵ@YjZ_ sS=FΞQY=o[}&{mq믤Fž6-Dcc ѡgѬ׭{Sdj5ko~+ Erj >[+{pvk.qjW0MS3i;T+OWu}~c^Ğmom\*ѪlӳKokךw iTnE~ӄ9yEڛK+S=EZݙis5FVBʶ[ȉ/q YUԸޛOsSZ?ٵ-ӡv֯V{j]][*5!0`""OXvcF[DꚶQTB>~):uMFZ*j5,B*r]mg_m0ȏf׹iۦёU~+RC*lֻּ^ӱc>_v)ͶU5{=u.nK%9E]j|^49W3U$M˷׭Wng ,Mաkڋv{m]}}]dhor6{Sv6V}ƼR)""ݰ vv]6T:TmVBW,I__)] zwZ$+OR}ZOQumjؽ]u )-^[j[V:_e,Nm_gZjjۯa1Sl[*" EwȰׯ`=!A*;jVғk{}Y+;{-:uE<T`[w}rlv9$(%TB `V=7^Hd&|(0"vx6CY`E'u srԻ&Bk ] 5[kD9dr,)"NcBK(,1t/N1D-KֺWv Romd[<@٘; u=T .vj-RI: mj7c <%wzK[RXEeu+n4TjٽVmP݅^7ӷokihJ.zOC[Zlޟ4XSGB]Vtݪzxm[U]kvK*5kۇ%Yv}Nf[t[e6կV^ԶUUzve-,ۂ_U}tJvjRكk:⶯cV]XSԵ9V:86\^Z1SYF؛ƷW^Q]kSY[^wȾ٩-`ueuk8fu I&LZ]oj'aB,׺},#(*`&ܩ`͝upfMwפk ^8v "-[yimmѪmW,lYPI`vZU h(VշH)5 64g[f^;)>K;+V+[F]e6{QeaMjexFU7f}ħM[~0mm kuc g֫zb>j cz‹Xh;[뢽6{MXS]I#VߥMk;Vx֫z{`Z(}v໚O~kk5ۺڴ۶kt=36=s,ײ~_֭"_jfnzYv ]*m.tU{CV-漳d(eCFjw_]^ J/qv5_[hl5mln[W݂vYiվVSzM oc^߮YefDB4 Gm- erաWa-()ؼ۬ew\զӧ`:W(ѽiR )uRTtXMň6Xb@`TfI`sA] k}=WWHkkThZwUzNJo:-+vvo#ÀtrՆJLŖGvvfNe-H~΂Pz۵/ӣ]+^IIkk5UG)kVd\m Rj@0mڛؖi=5=zk= UڠUNke~YsV/[j|Z(fZ7Ůuڛ)y]lzd]4B_T#1 26YwɡbkxXqub e]}2[i]$Т{m eAnϫD{.Z5l]ұ=^oWzڝՈfD BkwU^u^\E:WAu]X(ʫgzwLӴ[("wQiQ1r&ƥu!w_eAb2"قoh,lE]bo{/軌1 gv36Ik*g@4m] l>ŨeQwYeӱN޻xfQG"ƭӳ*5.+b."גjdպ&(Q5ʎ=r-'_Qڔo/Ζ5ҋMk0[skp։k~pp5XT┩Ͱ9k,]D\-vDT٭Rt}u-; S ٭v6l ir(zŤ_PO{޵/O[?`5Fɺ$oUҝj.Vku%\Z-cGUxzFRFŷUsnQ]sKWjխhsΫvbQ4llm@'ذ5[v}U把8>0Un-IV#k]vfm=6t=vzՀU`r襬d=j5g!*WV}__ mޟkbumvMzz`lffގ}_u\otkklk]=U j%*)ԪLTg:%#conc[mZ_.5w5n4Rk}aS{YZ,nÊv@]R[ڻv.k67%u;)zN̍_W{+r҃@Vkm`Vrvx4mnV{u!]j浒4Q [˫XzckPv2kޔ.zS^{=Slknz/۳nܥݎ'Ktv[Ic(΍bv5~4RջE6_viYlN"b{:dQ^ՕU(m+(_ ױ Eխug,Z*+@UUփT__q6V@~}ǬImp2ƦճMW֊쮺ҕ;5kuo)qYG_kתlvMVkܫb~:(]*}گbSmK.!ՙ3=meśKZviլo|KX"%Lkx:n\c=۱j#N}eS^PxudXkoݱjPWŮOgVޥYZ(۩R;4>+VazWbs;UR't>ͅH hMzP{m EZuZ= ;m]Ysl]-"v{꭪kl]i7*6)GD~Ƒ[v͎ܽi@^vwvoZ3KXAE6lӯ֤~8[.tugUٹ6vMj,,{3{6Jۯ_jW~勒ëU`E5v{*mZ@f§>Z62?ˮiXYX#vTԩ-ܳscYVvoJQmzTWm>krNjXյ5[/v;VѼ}eOC;-G!R̝Nl JCwkVu {](.z_Eijjw @j}S+]K_I i+EֹWP#mVl--Zͻ,(UZ$j]^nT4ao5= l:bGW%ߩg}aljTڊ%kjJvM:RE[ckzBu[Nr/5lTպ~֠Aav?MZikch?or"nZgm8Ԯ˽m:tfzlğu[ɫݖoJګbu7:/[6ƾT^ȵ>gV]ãimoezUvkںz:׭z?w3^cmW`o lokr Q\+}z;Zۛg֊'ͽ[1ublg}CZg++u,Vi]@{,6͔Ɩ}oZOU_hm.uG]]aohP֕TtbXj^WT`̕_wTLjj*sw]OQ[alDG[FS\V/mk^ m?zmsiɫ^*; c-nND3j7(b SQ^vm,vצu-GMvT׺]kZ"eI w5] [Kv*Z>;)v-*kO;;T (Ь}ߑf6hlUu>5k֢좪Ufkj{Dj춪E6,ddՂ)طەuTWݺn/[^6-اjŚޫD-m)HWb&-Zmޯelt;{/VcoWnkݮjըXvkx*{.d?[٢ٯx˲Z@2?*ZevV(Rk ʫZ:w6UmMEZ4T1٭zuIfķ[ WcZ,Wfna@|@]iTWYD5Ub#^*jM4U4{Z"kV? ͫWj4Vzn:JgDG_ӮGرl7DM^6.n /_ UD[h[`{+d+bg^eYMm3-b6lv_pѡSuݽ/6ͽv`iRzK[o}RMM=g۾56hQMlSF(3n5] k Xڕ֒6։ZU[Pe%Ժ4߭UEٳoWa^[k^S}j[nm}ƳXˊ`k^mҫ-ד\+M˙cR[8M0_q ztjM wjjfk*_]Ihk[j(\S^g++bڷ^݉OiO{|y+ӫU]lmuV٧zM]o:mKWM,U>AeFƥ +jS*MWumk5 l 6d vk,]*\)sd{{Y+]u5u[nX(NwþYU룶k]ڨcѤ'UvτN Weul?j֌R5k*Ȭ++ӢU;moo/jkѭVɛ[w3+ٳ@zw[5]_n {mYuh[kzjMV.kiYٵ/xnvz o&۱peVP2{ )VרyObؾ?3gשoRVKz"jfkUnz+%Ytv߭u[;_cs`&ɳM[vJշQ=jlOGu$l;5vY]iuONƠ.lRF^V޽Cwb2xʫnol;OQ+`+eح_WC]{K4ml^ƪk\um𾦭7/bΦ.MoY@2beİga+}_S .XZP5QKc8>_\Y^յ췎}vuRBG!6%/ mZ-WVvP:j2cĺ5dW{_q5Ӯ/{Z{T_}mjPఀ`,pkiWUm"Z./_a»7Gkk﷚o_쵎/kc{k}; #>fn Mb~ٷXN뱫Mz~"=گ[h؛5PkVJ׵WqصޞijԚNA u[:~S6bK/Xr(y(ʭ FZ5m?6Ӳz_;KRvSRvUرZKPGVUYRްxjgy{wQ]DO4m•#Qgvԯp6^Ag/3!ݵR }}jYmKRl*;6z,lLleL&ϙΉTJKzX.R6'բkP}bziuz5M[ʍzSjVŢk M}ZwٹE[ߒjVj=vzjun{]zV`[7~v+tOBJ+ sfԩؚag)]M{=Z׳howѽmUc_=Etj++-W iŶk6Wewyr;8{=ť"Sbl^+WRUKmE[]m_m۱e[vϫvT tWjWU׳]W5j];t*4>[v,PQ()zmiEMq M]]o=XkkEr o65o_M{ofƀڛMAukbmڮujdx׺a{YNff]h)0Z=٧׺M8Ekg+]bM#X-.cۭJ6vv馐_U=nfQN Wmzh6xzj}{+Z{AX{?&Cm/Y+`')W.ݮ}z^}}@jYfkj=sq6^ГvF^cikj6lYe5 Stj 0j&=f`h+)s2#[5nʽKł~f_j!֣H7jo벮m5uJ],*nRlZ_ Z6Z;]ullѢ}e u@Nk&_lĩM[_kԲksǬ7-ޖ7ܻ+ %b]쒋iZoKVk[]۶;zZ}[W_MiZڰޖi괴l텭YKj]m_KF&rHJ'-mE(aXWfiUSwv5Z٫Ekkl*^4ۦ{~Un[6[o9nS%E%ǭUEqiYMQQ7ݧ 6#kWUwc]6E.KkeQUgXVu=%5[ Ur}}]Q޺Pߓ^7s=W}:{)۷׮}׮k]ib5WQ*Rȷ;ׯU{WEٮvk(khFU$-}_qSKJ6u?=M@ꟍKlڤС٬eu+mTѽUh]_n&&l=aZ2^{EKVB'Z}6ilj_bz߾u{2-k=Ũ(  %Wck趗y%;eZ!y {ׯi:v7ٸ-B۴v4}Zvk=v4mcVUl)saՑGGP-e4Rէ z_UknfԻniojMM']fNC쭊f_WqU+WR PSrb}vS=kYR; MϽj FφUX6VT Y/bUI@+_[sZ=vֺg`oP*pk6i>ٵ]IVۭ;۩}gu n~c64=Uujn앧}D]~cUtޥzӶݝڻ*e7-ulX%6/QC{lnz{olk魞l-cU&d-T+iڽ3+umëQeQzؕbT]P]׺KBRlV[!ůN>S{-dkmfsZ *sN}vuLnmەU:eزU^liXt[zid}nݺ67i{OMykt}mZtYYΞjkثkW }^̓nmrk<_ePײS^tcs-ȳN=ξyzN~c]\m+GFݒ-[V&Kj鵒MvVYB+Ȇނ=6*էUyZkyzu]XYNj_^Ύj5o(z=\D *{:݉X32sz˚7k%ߒ.{]/DzŤvmܭu{ۊhclW蛾v.֪J*DŗTհWm7۪붹-]p)PѻR[6z777}oOEv*g_˴:-tכoܶ׭[() ~UA/wqnqm=fC}x{vW}m vUR^ /TWYmڷ+;[U>ʥ}{v޽-= fv[nկGK}Z[c|piVAtZO]:ڷ_aV}[kzs=}UZWM_Ռҷ;ilNT5 Qeͧ%wɪ]Urum֖쮆]H{{aNUb$mkvNÓK]}^k{B]"e֟#v5]A}sSaJjlEWil\&[{67 E:ߥh]k}&TtEjѷI]XԪغKiױvڽm^|ڗ-Oٱ65:֋Ǭӹh޺ڕ԰R4ݰԥeFzM+%c\ bS5zkߎ۴l~-%/KKٴٵgm:YZYcշSJq[iZU47*i( `պI {lQ[Svu-{ /]cCemNR[ofJwvĮѫeON_M^iu[JHޣoCgmSJYvն=}Mr_eYoO+]οmig4ZF ]~B0?5+M4YX7l~NZ뫩c{!rn U-WpsV/5ӮðuUij]iaN}o5z`WSYVޱEDGm>_a6Wvڭ]UtR׳WKmR.2зi]uw5_|W~/5ͱoڨ^k6L2ɢuhU~Ͷ)F_N{1XWڣf{-6X],-SvlPkZ+s?M4lYi{Pg6ҏs.յY6UڶѢ G.J5ʱ{ieO] J=tߺ67,-j]Gעϲ{_PֺMWTrͽ4r^NSV{O]u{RkkwRϥeۨSSljzV6Xν]Z+o"VҾfGwOJKfݲ:55ʌ:ɿH~=*O}ݼj-zK]{1E\;[l>L1^͝m{g]+WU+r:ޖ^oV^ѧۯգc]۲ʆǃR[,›حE#-5gY^{5ݮ]Zv9ڬ2+;;[KcmY];n֎=WZp;Թ]hˤNR3jtc%f5q7.kޥմuV5Xm(ӭ:jYckC:4 [wrݗڪ: {kMkʫM+v5e5]Z_u[mCX,vG[wcO+vlԪݯ\lΑuJu6\Mv/t&?/mu,NcZ@pkb^˫Z߭UJUn(5Q*J>vJYjzc]{2>Kp^[kNw~Y]*cתjhKi)Nl_]]٭T7Snd N$ҲlY;50ލ{uvZG_y\Ƚ5ESbڏy}u~>z45 v5uS{][K}ewVuXn얭W߰ԸaQAVniՒ[huvb u_]j ~=Y-{70޵S~ ޶*!U4]E.0믡]uf7{UU㾵Wrס}VٻMuKkf?#dgm͝|{~iٱ6]c ۶Қ<0v2lUMv-o*Mu٬u=6V^lzdobmiyu7 w;׭n;Z%{-[]MZ;w>=gVԢ) UV=t{@nVUΚb0N۹+oݫn9gU9ة6;߱.>9Q -_}뮗fȌݡ^Ֆ_\k:;]u5U fdvrw)5,rɻYfZVkP2= U~Vf:E͍~4tw͏־{kڽt%~olm׳b_S%Z _e *_T]{ 赨ՑGdѯCYNnmo5GH /V{|MKUw6I#W1_cuԪUU n:Q5ijk{zMk5OWV]P򾮣lmʵ47\m㲖*54VflC]]cVgu ]k ^{yGzk/5l=l ?O[OpUaܥZT4(Zvj:tj5^-fV:$ }}BuTi[eٴ(7}{}}E~MVbŃV[rWcijvjez.f]b(MEjȫf^շ&թH7Szt67Zm/S}:[; /\F[uT[X46vVew_Oߵڋm7Y^Iu /cEW6Lc{^ba,W(ڵ, ]g֬kt]_U6jZ(˷]zn~-@,QZE"N)?۲ce{5u66paKJ_S1װ>ܼk@o;dϴkK3OO`}Rejktkg]4k}WFƆ׵UQv뵩KWvdЖt}!nfWfR]1V_T4s0,zslV5kSKP:? G= a4]^ՖZ&6^oK{-ݷ{4:m @J e6_(֯Nou-mhZYNl_`~5k Z.MOgSsDerʞ nk٭|Km6ڶ: vV5թp컶aJU;>K7гJX٥e6ZU_K*Ul=@lSZnU]uC;5^WKozf֫j'EU=&mPKfm-H{SpOe \'=u=v:SBYmHHw[Ⱥm  :6ͫlf]܆>I5[ 7:bsYw򵅴kl n`iJmbȺ5]:/iEƪ7Yco.ٱw4iѭ(kaQwedUNϰiݽah_eC{ڣJV+}}:VҔ].t(:m-Hztk6soZ 7WU]Wh֦Z%:%mjXv[^;w݄-K 픪/{w6{Wbvq5_vڞRlS_ԣY:}uA_`gٻZiC~<{VYE9FH[,K/Mk=wo%ߖjVj١|^wE=jz"{kI4w5Cx[:O~?Wjֻ>ݚ5֮;m+b\[x+Mmch}KeT[UշIwܛo8>[iZF{n]}nnc4YvuZkRд;ڴJ\%y NCzSUn]kvkĮt^Z^EKMuZmR`m%79i)+gR)(zM5YF|ffǼo۪\ޫr퍝k:5eIzOVT:Ϋ6 UU):V =~Za_۪N^mYvӁ=ulڻUz'V lm5KJKYckm.ukhޫ;]Vv׸mn\5jۤizSvpsZ_5Oe5S]ll{?WMoV[eKRmةhT2UVRݻ-yIhUνKUhYm5T&]mr.gMmtZoR,EG]:uGseDJҚVJ\zçߪm)nk^X,iSpJ[fQrWaʶ]{ B m}_>۴D6E}mͭu}vtZyⅻ_keTmj6{wr-#_5֖ft^-MT{hRF]_n\׊W[Ϋ{oD,ڴn:Z6G K9mvD׽nRYhU6WaeYױnb)bSjYktZ}n+}zVZڽ6i݋uSnUznT,:Ը{^֡i-}._**Z=e%KV}}Zahm_`WOWF}Ǵ]M:s[ vuMFk_k!Tm6 +kjۭ44Qg*XUV:X`=gj̅SVݙPRطa*{}jEUziJP+kJqժ%պ6kj\uӧT56P_\W,bb#6/Nv*N߱Q,ywXKu֔WuLeڮ6b튊ԧMZlUO~֥_#l3e1+ޗ}Jigֵۮz6SXk:.jR^BQeM 4&Umk~Nb7ZmڹkJ)h*oݹ)ַ۶Ʃhٻa'f5ضumoEvmZtg=52nՠsYY],Yj,l[1WELYkGx~Vk5AUZhoj.ٱ=۱{Ɲ-;lDmpթ.[j PGgtR^ƭB (=7Q_}BT SJ{sMfQMYYjY(5\KuSBvKnYQmoUb`ʅnU_.;1jj~K֓Wznb((M͍TG vP|΋NEaם)yHU^v,Ȗ[OU*UgۗԬnY_xv wUUVԢJ72Sc9کIMeZ(%_ݹ+ kH]F9.Z_B;{;;Ev AէMV%=թ,n!PaE[OPNn }ܭyc*{6TLkkQ֥y7֧\do@+RWM[MG#Xjޖj[jBFEu:vO&>+GIkؾBUK͵Zʔ+w,e_]wZu(o/+&JW7+2&:R~UMHW[.)VVY?n]Rǩ=v-ҭ}u6[Yvͯ_fUqh7&ŎٹNƋc؊5+:Um] V66!}}~5d$j6kl]۸Q[Zcآpso&u!_Wg&l/vիVڳg|EfMۛ=-AO%:굵kckaioij7- nTMz/ r8 ,aumZ5%^>a]>kmͫuTn:RwsnFŃj3Pب-,Һ%ۻcr5[uVyŖ>]NmM͊onUu:ں abڸ >͕߻} )z{ceuK+bճtuS}BlץWuRgu(Ri)p⫣Qcnl۳Ef幅/T5vvkkU4Sg=9 j5fU4ެVj«l }M0(5ܷ˺o,_uTY{6 ݹZܖ9Wխ@+ WU5}vɡ}COjknգ\kѨX; uKXRjv]-GSٿ)ڦjsGm[ȍ۾+z4ٱMg\mZ[Fֱ+k_b[c @Ƌ}g{[?gε>? vZ^-#NknGĥj2'n'l4-{ة^j5__m]avnRﴽt(/v6k!֢Z/sX[iZZ,_gywnz~}E9oŬW;k]jwYVlMhuu&՛o[WPVõSH{._k}9MUz]i悯ЕS-ƻΕFi^׺soUNkn6Vf߂lO0\5jرڮ6כd*h*w%&*DaՂ]SZݛa]u~;KNfm`S+n9pj_CֶQHå5+ljM[m{m}PQ;5?CJ }}5JvjujWVDKu:{-vQԶM$aufqeV)ҽQ[}7a=,7T[[r4څ,lשV9i[m-EKʎkQBݬn׳B5ǰڶHd!Zڌl YɺXfh6,m}Q[UݑRU@gbjQ-kLWm:,,V}WZm{[=RAqf{B>u/Z_Me(ՄM(m}Mju}]}_wbCuEvv/ӭAzpCmT *Wk]UW7 t5+-t-^`'Yͥ۾it{Qݑ/RYa!v-֤ u×uo즭@>*OOe@?R/p inX5\GaIB5j>sCj7ݖ=׮ʴ֟bb[)kl}֯KWQ%֠<W{vIJ/~}ףVmUlYWѯE}=k+P}{PZMJmrj/ ʎF (4K; _gvd͚آdTgbkWM5[SRVț{tl;)}MJ l~>UW]MUZ)ܫjeŶۍzߎ[M׺wfPje^MjuI]un߾sC5^3q={kfu:k,ܰYUKmTءCI :t\YC`+ځk.B=y!k-tuWy65E{=[LkWSj8Ϲj=jk못k_Mtl=ΗF-4tn[[imZkױlYt-Z*WdJ ƥuVbl5)d{/T6]ihkel–Sjl$۳{@MOc;nkufpgU;Y5o^S̵ E{hcai[FWeymWܶ v u&֪gkGbgiJti%&캄zvfjP[4دK[M4uᚮ(]jWpru&Ζ Rߢƪjز2jԲھ;o定~mUzUF&E7轗{ eѰN'ik{=jkp^bZk]ڵ5}U_R6.zi;3 *JQUv&OcndʹV6nmfꊱugRM5a;٩YdDkc;-͍ofҴdRkëkj)Je*ݺU}E"Vw% -Zk؄3"bɶbZoXYjzl)M5gCm5k"Զll;2-onٻpSCR5mw7RgFvnkTԸ_PSC}X56{B}KG[EMM[n63ֶZ_i {V뮭^hyFE%)Qv&k{a>[mizB{{,5SIwul䲊u66p U-=K^ͧ~hBmo۰%iҭZNkUʟQS%M^WʴXffLwmm"*wءYIXϴ=Q[5ZsNE/gPE O{7>ږU 7/Kkik8mUծ[ekm^HZVTS,6:KnJukFnj[km\#fjI-_aFZoac^hmYcoR5GqE*Z [6~fmOw4?[ocRjG6wGVl=W7Z?ec?G7k}[vo[?lZ_˻z??ͱ+oO˯S4M_O_?֯,O^Bw6j\?oGw_]ѵ?:*BA= H SS+BY_?-CB..Wn5"TaE&d]W}.2,|\kA>5QY(WAЯ :U\#f  T~U]:t#-#2E0Qa^~ABIƽ:zqC=c;s^r7,)GNđF+OCO\g` cMmSC$lTBQFpӯATSfdđ66']"(viN3<%ڌ)BD~ q +$( ݱ3h,)BڂP#VXéA!OCOT\NFv(NIJȉW, *O2>\)Sf::L*B!S%JC?!hs'pu  *jD> W K!(zQ̉y MM(ʍp$q+9 [FLvп7/x+^"6j46&d8$ I^e3DU$'"8 &Դ%] "Y2pK˷r/n]!/)( M3RP|rRt_2F^Ԋ+O!G !,XߙWdxmXSmpa;b^2B eo'2n5,q8C":^Ew8nW|Y,op\dOʈXJ5_i*IlI2DQ ¨sF 72 #;Uܞkm".`]3cmOu}CWoՒ%JġǤ%RQsҴyq(IR|B_/pTrȵKSr(zj+T]('}vۧQ7{[J()5qFEhJJ##"0{qI(j&}#i˘Ѩlua+G(mBWi5nH.ix+_3|7 Х̣E wOMƙS!iqQW9ov"vp#&Cӥ,!ռ]&'.mnےɺ$*xL۟, u^1w5'(?HWS"Q [g/5 mMjlwY1q?;RQ *N7Az+rPBTYb-[̝ʣrv1(@QjZhkF-xZ{w~Q[nZ0קُ [mQ%cٞӧIQCV<4֙b|sO!P^N+ g$*(ƽ1HCs6nQ(f[mxp҄y##Uuxc5yoJ2JʱSw-o4iW~FC\@]{JWߩh']kӄN ?ny}-hQ+8ncyF!&4ss|Iת^!dUV'4 |6m4Yzb"|۷ am+[^_.ƹz^Trd)c*b3Q9tyPpt5NI㰮^VAGCRU{LGRHMo^b;fJ-=%:uy4j1ob&unbot!Z;Z־%=e*|jm:Mi9\7vwT/%bSFAĢ]KiTA͆& aR'(FD̄ՊbZ vwNLwObէy7Q[jz"Q1F5 uQ~NcϪKģWN{[OJ@U_~lV7>GnĮe歹o»m%lȟGQv.J|PrK/v@nVF-&"j$E4~խ崹m}erozJY7zxխr*$4Pw+yqUKgzOjBK46^VXײw7o)[D;Ү+yMck~Q>N!.oisȹNEβjS湉ioH=>2'T"n?vR؟.\5jzf%Q<3c? kss t&WM(I-7+|^oh.Dp^5Y|^;rZAN1nƮv nesn_ A{0$fEjW;;︇4(Sg3C-MBu4%ji)e]]Eج]=OSD,T$#]tJ oO`'n]:i"V}:Ӄn\II a%zJ}IoYo3WDkoϸN^9F۲7EtҍT}60zmY vpҩ)hKC/)7=7eB}dw+7l6Q^_ĵ3OW_vܺnWw7ծ4ڳ]+Yld:fhsΰ%.Ei{SoYm[u_5'[M_sIdoK.2 V,i+wZS iO/ʼnyN-Sosvk8.˄"h_߶Ү2UjE<u IfG׸o6}}VZ9tSLo9qc^w~u קG4k![ I(-4 oKonwdiW-kwGQ%wJBlsJOE.yEW{z*W9ڮP;)~[}WV}^+ft!s=²ݻ͵=\QM9Ֆ{ E߁S껉6!Hs j/'i =}"Cf+ȮDuPy*.H~jts4j\uܼțU|[/BmVrUk[>7>6i[ZWڽ^m9(Hΐ%mۺם+n|C"KaZڲ+'wO/&g,I;} ZZiKūDnC[)ܺ}DOi;4bӗ2+BI2)p(^T({y'st%|T) H5}5Zsl#x"\ʄ.p^WwvZ*j5wA P됭 yʍv?OK~ݺXF[L䱥uڣMGSE]=7)[ l]yTa,<1]%sV}DaI7y%,%[9.VWY7#"F7J7ߦED;mBBKq-: +5*iKxjn86-&mHk~[&]WüǑuZ?qoige^+hd_Ǯ}cܺ֏Sܸ"g=N}Dn2#ivԕ$Yb5¶Ȳ^ߋW]t[$t=Irq xS^ 1|Uo7p[ᆭ- JZxvi!>(fjy:r|#wޖ( D*j)DQ1QA ?t!R^7N14V[ "W AGHK2!fĦw{n*"ҹSWq ui V&q.h(1K2CU*Z%v_BNwrqo$%on3qZZ[ǘ[ f%Awm0V9/[kG/1WUF"33Mu: 3'x$`%/izwpS%T!$C9iv15/U(%38(k׃)vTҳ&*s 7 r..%x"Id:xøi>oJ!(ozF{$th+V;g8qW9;lYn)۷ʇK]mJ_6uEwԞ$T„'e^ ,\nDCMBP,˚Emٽ/ skDDTPHdoR" lbTSI;I5y9s51B?v*JrFG)B^vYjǤG✐I(Y"o#Jfl+RڊwyJN![;o#iq'tCeƨn%n;E_.]wh (15TߔIكLFP$'LմI$ڥjZu%!KcovGPrSp)m:(KUù;GtgmW3*vdV&.]|G%7 ᧃ)kI(ÙKyR &տpjQU2ūE&CW!ZĵqYo";L:ƥ pZߨk"7]nDAT!T n&1E"7>$f$OK{>~f;VjGaw{vVY*Z<$?/zBs# Җx,7++L۽7 ,BBv(n&7C ō]=v{!cΰ].ǰNq1?];?$_O zX$02 KptSf'zydS(e_:N*S#vI_ ?<6~?ЯK###" WO\xcTS3:4%ڍoTS#.ti„WGB 0P%%VGIQb+ӧc Rp +PTvc~ÝEhffQռCPFO+g doy 2Nb e7/5k(:Gvp Qvd!&`OƣV.W+>]znD1j]W?IgZmy ;t;(EvCQ2=`ʐ(Fw`؃<(;n0ߪiaL)Fg8#J7akwjW ZF.9"3!}չ[Ig7zeu5q |#j͒H.dJ1ջ rM ZHua\T¸ȚI;SU{vdk[V?)B$s4*u`]XNF-Z&;Eĭ#ϬQ-LJuo&(CPF6;6v Q i(}i`N r#xPX6J)p>GW`DjKW[<'{w5;ID2% ْN;v(V3di(~K͍ 4XTA\emvئ5dasU\R0>Z8aTFxF=`bi.T`GsHk F>ǣea3=1?]&FkX=8mqXG:cb9Hc±6mjG_ȊNuџjvz:LWWvVN~j>D N&&Vu"G~Dэa]~}z'~z&}1铳\hVu>k:>+N#gXWMeYTj~OUM gGэ~@cSk?N>;k#¢=3_pjji~oF_q=Xf}c1uQ?GW>OG2GUu(u V5>D ~jf=U*ѕc` DtP0uVGGj'яXF/zGY|}1鞜?|vP3^WcXWeMcF0c非eP}S?#k:蚙Τ5єe+c[IwP.ēT{u6(: #h{ ``gX粳(l&6Dl]>}?zpaYsF"+,z=uj=TN5eQ d~]5~}Vx"MHʦUc±ΰ=tL_O~gXM~zݶ9@8:-lzs5|kAΧgQX|*GS? *j>Nauz z0 їʊ~Q02}9zcmLz1Ecș6zcuWDz2lMe_ 3 P|UFYhxiHY'~Vq0f5 n+g{w+qFTN@Vj=*}F%zƠp옯)rAV.V[ 8EQa-O֕ k;*T HRUɴi67Dl===U=1XzeaX>LMcaY~ƽ_'XzqхtWW~3}>uzcղj:*hǧ*} V?'PUR6Qr0URjzT`{5l}{PJpbI<^r\;eMD8J{̭ liRL:p@eE\L;+V:WemCqV$ dօr*P]aXV^u>aQ荵W]^DmzzpG'D0_OX/1GưEGMMcXU~G:eaQч>GR}11яU 'xVU1;v?b`+Y06 O۝PI[V\$3K=ܮp̟=>#:5 ƴ qD5tgײMMc< n<#e]1e/B֧m푰TKn=0r2 pԴB|GreW_hDOF9oTMBS;GZ+" WTj4 Z z ,;QmyIH)s̹jHĖ'2F"-bsLtGƇȚs*OYVX|k vYO_=~@"j**>P&*=8eX|6V8WO=k*5ENƦb3ϑ z$]tz"=8|_ ƽuX z:}8|1Ff7DuҨNGx>tH: ~H)tS"N-k׷^c]{FCav'IDPS$l'::h@q@$ezzpd#8OԒ2WEXITԠW75 u/Q-s׷xN8qwT4խ@z?/}EuVk*ƣ>F>Ϧ-aSSꮪ=^>GUke]uz*~G^DTg"' 0'Dpʢ}4}9z2gXz 6ԁYz1+Ԝ U084xHQqnvBV#ŕ@~4B[oN(3F('j7I#(5Z%$ եr+rk }F8gkSP霽±\AYGPD۸m$AQ^gF[Si nԒd^٘'@+*~ 6|3Ƣ_EG~^#V5?uV" ʏGU@kӟ}~Vk ' ƽU z:=f+ӆjkҖo!֤`h.#1iW[SΒX#ލ$At6ާGtR8V'aJDLܢKlIZgzԺ_YA_t4ˎ 5 kɺp.1G1V FCQhq*h1Ձ#/jtT7AANjlScad220r4=S#VcqPHv1ưL}Xz'+==a=z2#?EL'q+ Qa*#ʺk*VT ]Tb>Z[e$8uREFxY>on*jŬ/Y! SW,=>d"Y7w^>DpU}1X?ư~DVU?>2gQ4z>D1G z1±a_gME±5@>$1=8Nu=ڗ5%pq'am\ܻl=7>\r t!A,# r48a^YfM[PF>Pe}l23{QghR< E.0.)Vz=b>]Ԕnم8EE yKqw7+QD7Tq3=[WLpu/۱FM|D0 ,avjD#T۶jr꥽m\Ӫ I02VZC 3LW&#kP鮡 a|kz?GʁXu>]^I _хcX1˦:VX':FB&ÁVb ,Z1<ܣ,魝W9 nr~]5yk,Ϭ2R [Z3 DL\̞ӭiJfZ_MB[7u*L=:ubݫCAkdbLj[F +?bK޲m5*\ _kI c`LN;=V$v>ߢl1"#V&tZ[l^a@ gi^dgM3*} 3p Lt uKpö[G[Zclbfc]ɧ#ݭѧfzcWO?D|}8c>ugя|>$Gz83>Ftz"kQQP~zqcF: EG*WEa";:k7geȖ.mDGh(ۆ-]^ WHswAw6Ԧ'%NhN"hH$o^Qd ;ŋ0nN`R_W9 i4uqh٭W=koS7qPb #ܣq&ꠦA !k`Zn 11Ŷ⣪L&@$cլ@AR^{_K i-6cEXki.Z޶$u2`$E; '}8|=0G]zќP+х|LT#訊"k*]^>zqcGih8v4]M*AVN*"^h|>=hBєR x=dX@H,g^^j81_`ӊ=֛0ܱ)#W-ẉN pEb. łj߯*nզ@7N{+ DQH}i4A.\ R4J;7+9kUņSj'":vVSRAe&{Mhh FȬWHf(كq٩A@7.H7Hvq?߉rDGY'>F?**?x/0]mtm@^Ht#}zsUaV8 ?Azn>NE8NyXWfꥹr\u¸m&&zR23Z 8ϯypg*PtI)-yjV\jDR[Uȋ1wRFdnWGԢ,n-`-hhkաZu {;DV`D2M咭8w|4|(A xKZǗ;5H08cZyLڵQ8,+d{(-N@DfC)GD(xZH\yIl`z!T'ہ>E#FmVxL]V qV(vBL+/Eg1AN{r5+8zsx[?MR?WS#5urG˚Oo+Mc~?&?aXQuV^oDgӍuV54c葅S+я8@ hU8 ң.:N10(o\3-+&1eyR {vf|٩oדQ}ֿYT# Tzr5Qj,Oэu?cX??*ʑ`b#1ROPƷMcQQ]UΣa^aƉ L1=q'DȉDqUBհ#1}e3]j*J*FG=~ ~taQ50e]t~O 5$*@NӝH9F':9(AA8R`aRF=tv1b;Z4ΰKvwk^^pxp0Pe͉4d-0@$ B'bW/bpx&cJkw-{Ru[#2iso*`%ǐC~?m1CnH?D=(N4ɐ㠞q+ չuLa`1?"`ym $ŻOl5C+.L,s~\%#P[˹žU @`c#A}Zi"s4 W LD5Wh=>Uqlj=uҤknWm3R{]ޜFa+!X1L"~}PPN4xEFӉ>Faac+~q¢ƠeSD /@xmQ1`(Fd^gJƑP0u1)\MFO~כwآة3E4쓨~`t&=z8[iAW#QKY#B2X]kۋe%ݹoˌ?z}%^ijq?x{]D4u d+VfcPT4˭8)J pBv kڕ\Z;R۹ȌZ6Ɛg&Vdoz$8oQw<& R) 9٧F;i0q0;@h+F؆FR\;S/Wi uDh 1Ad)MGUi#ZN3(tµн5;# TE[Y\&6r)Flg{)շA}X?±+]^>FIƅeV噠CljxY26g 4 "!A+RH!PeD8.?[:D1ZYDuqJ@xz 3.4Y.#PHLT$HeiOGf) ^7gt-jFkOyk=V1+`q hxa;~zӼ;?EHθ${1#Që,p!H8R)ׯPULOkX1TʎJ0z8y&]զPx2'9GŒZRN@h=I|W"dxUc"tw`IS)"'--ńb;OæĘQ{Tj٨ިO^Ue1DĢ.uַYPbT(r[蓕V5蚘a_/_OQ_""*=3DT_OȊ+o¼$9U_g@iv4%MHrsn tl>`i<,LknUcUBldh#h1XOi,ϔ}>X-~ XXZ1/I *v֩f?ZUXd`jp O[~yWlpbĭo\#A LvlȘ'g&eRY/ڂ:>֎rIQhmZl=KAd5$n/iiy,VuWX=]]"bUkR ܓFDw_D[*0&!$hi{^-GDFU QD ]Ad6M` 7^VwoWTs>kL"I~n@+hsˏRqIjk:z} MaGEA O>aư?NuGQX|ʳ鬫D"WG]au?{V7?c8{Vr$P]&JD;Nڤ] e.a[rYr_=jlFF;4mq@T8#(GIigݭ1j:L$CDljiszfM$SP'uu5Qk % 5Fv}~&,ӕ8Q7tVKo e:zMu&>Tчz"?+>~}e΢D4:OE9mꦺxljҋu` 7V*|Fw*Fz[٦2$boeO%tg=k;u<~az?Au1 +> Ǭ˲ •cKih2W:;FN* XDrW+\n̫C'RYeP˰KXW5 oZKnyo\T㡻.r-2DgKN P#ġSHnV< vV߭C5 =4L?I̧+颠1ǠRqb#E)[)Ӗ*@iz`="bv 9xڀ#yRȂ() l V DOt5#VJ?N+ Ͳ?DڶXmUo٬1 *f6މQ՟D=Uȏ5?P*=}V_ зhN(iذvF'ZoU@%P g #'(Is3 md[HKfO{V\\B3ct+v(2 wSEAO5Kn;7*?'JR 8~.MgפFPJ.9@`N][K a} tan5قc^֡b)fәb61pFR0t;=)!]S ݧK"wۧGP6IW Uu+R45 chh#j\;kR1ZCkO[PpVg8m'ͺcVcVjݣmNkEEP#eFG$AV;¢*E@}ZV^>&.\@:qb{5mx4g}.؀H>-=@Gיq%OiF,6jHL2GM =8z'=1XԌ9r8cSt|k }=?#}=u} 9YW=Q:hސ "54*Asؤw?s :1Y=E`h iG#ugd2F/QlF="wF Z+R4!vb)Xb{T0Hه5LgX Y1Vz7'a2.n\R Ը RtvKT-N dXvY|t<`shstl~VP{B[\D&:pT!;v(W0B1f0$oxkqb:T-$a+νxin 2%b z@uaY$@z˸t9q5LJfk8>c@LVB#S6cXmnTF+A&8KWkֵ-\ oPUPEKmFQ; +X):;5yV8(P;Nȡ7YB s΋kGj~D?'A=>F?#Gz:XMF>5O^XU53D?Mc0& ~j GLᠨeh2S= an9og]h c܂ʰ%I֍fvji'[w'2cOsMK3n dEPA8Ò09 BOkWkv*x>6xV=`LBtRnߝ_ANl 4r!Șjj.Zjiu=_Kn!%Шթ*꣒piۯ5F73yz~@[jF8祩PئKiR"6$l\1h鈀GnbZtUȑޣmfAs)Z346> "$lr`L765Nddj `b Hc*p}`W`@vt}pԋe JH1#( >HA;&@-UwT ՝``Q=n:M tswu(5\)3zBV83=mp#XbїggzDOk23@8:XN?+RXթ)b%fZ?6Lp;N$>_J@ڣzI0y}f lfD(cF\y8P[UTiTLDg>qҳZ`;WOp]ܑ&hq@\T;uiD}H+^H0D[=QAWrO{ݩ oVTzjZL+P8i*  YK*z]ۖhQh͐jcgV@}ڇV@ci!~H dj WHAVq&9 bUfLKIPq_]N&H~Ĕ`JI'88҆N 'h"TV+p. pe3Z ;3T{+d1T AYQgmL B@N$ZciޢPg#@j$c^k0'D580B]`j= 22f2dATc z0zDU9WDlOȏU]T NDԜ+3Et&~D|D?'MI$faTebd%=jq(g~N(*F֡ӉF?@ `_DL@0Z"_9j2oTW3 pOZJ'08tꢖ"%=!gi:FMK#j `'Q,Cw dxhU+ nK=XS!cxQtu7\%_ }J\SEoVRc?Rto:V}k*p f6Vr{AOe674 OmTD)9RݜxlGV#m[ }[@7 =FqqUO]i9"~C>Mm2Wa6+/b0? ja*k76p.xQe0F`~qX1^hy禶X#b;[A M"Te4ʣ[1N#/]RNɦzxҒ Ћ&`Znn ۮN㢯cEVڝGk᧹pBDטT)cg]j<9dEJ\5 OQnߗm{M=oOU*` p>OO+Oz&~_GWXQ^ٟ>D|Y4,$ {Fxm=Axā=$9c] z{֛EAc3 P81ov<\Ul * #ٹ[TcD@:J֤0c6ٷF*s+Tp~ Hܠ~MU>فƂ:{ Ł4ډ ن.4l uYs6Ų0*,ʑfXe A(aq*o_ޮu9lf#5gLth8:Xvv5^@e]s R4U;pq!ebٌ3ՠOdv8H;wiq WuVq)H"r1Bx~@ m1Mr PpE2/ S!Y 5w?hPQ>I:A[X9=1#T|2QՁQFDb4 NVcGY.zBǨn5(8ohk;#GEuzqxTNU3UG4Bv4 pNir]\cJpnS:`nh_nxP#VI{ tv N:Zt7hB8 i_ĵEStHWuj[:uKa! ,KQ')*>P6A[SV=F֮SXph]ǽZ%X@Й:B_ `j+O׮]`@8cU#!X%V$qiVXw[6оaRCҸMm d4 QUNDëe-87L/eL-ϽI#WR\C6SBmi$~QM ' mԤԞ!~7Nʆʁ%‹`7XbM.4 J3@'>HۨunP/;ƢvI$ATQ&2V[Xw y!v-$3#D!&q8жrqPzqTDe)npշ #чDz#"uV릠e?+ѝuV/?L_*T D3ƽuя:+t2Dׯч[3ݡe.GJ 0/Ӗh1DIÝޫ U pYo~y54DI mL0ޢ 8y2rqEJ1 mj]@c :&!;EpQa D'MO@@QGGH3gbpAU=5; q꣆# qQ~ 0zh81#^.+f4WR*AkZ@3r.jK>k^0X 7ԧ]=ZxmI=ˣ;iX)FmPa٨ocwh|GG壎 i=Cpo 8JZ-::k^;Uq\`ُ3GXVsZe 頭fDj%RL[6HD1Z:BvǬT Pd0s݃9{"FbNP{sl(~z[`Ӻ}QrߖH4Pn2D@R[IIxH<+J@FuPdII@K>|031բ=} $ڙ 3P@#oE[p @5 :Hp8v2GMMa@Xe~я>F.EuUcQPXa5T`MaEt 6SFqSI£z:ǠEy׆`-BH 9N=&,ۤtJauc?c+e'SRV :)n#W@f1ֵ}XHC!{kLJay>pZ vPs@ MXNPih l}dTCh-sXWgHf5=#S9ךܴq 1 a$\4úC9ڦ,ĺZlI:Yg',oۯS 71 v?~C>=MLEME=Tq2 DEn`@)=#.qZ4`I}bvKXc+LW竖 +j6&rvuO CDK FhAvh)2XS@ A[vV9*H(Hzh Ȩ$;J.bXqʬ4"KP(@ϊߢu' P J ,O㡫H/8Jg#gj(3 1Rw}4<z;>hjqwU1 Ɣ'x(y6@>UvՇ[룣H24 &6 A ŧ.s~auV'+XuNʑcч}/}0qZG}8z:O6eXRGU^c(8CD1YeXeSZEA lvEt5fvY$@Mi t 1ʪ;u+kGX@BNkڭ#mt?Mi8m?uP#w_ިzh ZLC %NF+@LC zㆀiȢ ƌ}ZA@?PeA',](l6%&ϦDAe f~j*bOa 1R7HkOH[n#ʜu[wH'S8#,u(!pc,ߓj鈞Ǝtȫa#WsAJ GX?*߱E b@$T-r|p :xVL#pe?5 njҢsvm`t0e̬ShK4{Ƴ>MzKX#.aH1D@'|TwlȞ@E0O:8X&;in[O3gw!I7r|GUa+xF2shH&PrT##Rj`WA~M(z,qX`[8-3A9VDmII] 7M Rbκzqc5')5aENx#guFʟDVEi1QeaAiUcº#֔wA8>lXkWEyRn ȶ&zDecw'P\RĜu۝뚄 8Z(VFĴg\Q #3Q 3CP嫠Ȁp0(Twh1icR3;A:z*r*rU㴎ٷf4 |ު3ahxTS4mݒ0;=5m[yTm0e+|tlͤLN<1O#RXr*۫AA޷ۈz3Ă`d:T Gʆ@#ň"E03Q0.H=HǠ5if;Ag5h #Q9w(\a2bTRc'aeu$/ E8iۭv 0Hb0Rp;OyA8 8Fsj ǵCƳuiKnA$SF{T֐NX|wAhkOY4DΟkL@55mgX?&*}*Ec]cUtVƲUc@ʲ( K04ŒH=$zD$'=I-W{ި1%'&L4I`_f)g;ME_<[s#yxgOV BSb,@P kA"pǺ@I* Duؓdzj5 5B9QV#c@cꬰ 8!L3{QZ\!0캲*c/V9{ Cuѱ0xJWpJ7},k y֝x8xhaohVu";⢑E2 =QcV1<+1XN_h?j9M-?M;aۉfʀtT#c+'ݬ\cGQ$z=TIgg G]lOGG8?zvV8 mu2£o MmFEcSS1QRk,&)mI3PDޣ8bscOOwR5`XlP;bD*ǴZQh۶AN|uq|LXjOc"uIX@ID4e4ۉ#nj`d>/r$8hBqZ}Y_)e}Qv7{ҼC_RdVBv0~j`— & ]4l&'lVotHڥ18hѕac۠)g:o-BQ4j V'H#j.&&j=,ZْSUej%()/aPW\TƘ Si)TAkGa5')F<33tLF 45820Ur WfF]GRm6jS2V#u o)U}U'4lFT.PrP3f?F4B1RCDz BH˻Zu2 `Mih21f*m}jԮ'mHhI?V!ox(rlChCNF1x@apiV\Rܶ3(a=s\vCN5ƜpAh/olP8A4c8MhcGet0' qj4ae1SVjr 2DDPm[EH+]g5k4AΌaX?N5@9OMaߪ@O鬪~V鞚zh9 6kMEaX)^X&UO^u1gh&&[*[Fޕ|4aqێRjXٷ59FzJʴJ2-TPmeAb:zq&XE;+&i8"rSZ6շ-J} `c{"ZwjѺP:YH˄~-_ yd!CV#}:S i2P8ibk`zcg\F_iTce,nWtA(F*^'W1\>+DVTiuvXgA^Ё?E$.\c(*Nj.Og:-AMJJt(EŏAcK}JZ\%WUqm.uܑ'ugE`:DӲ}|:imݥ9DAF$g'3Q$Dd65piѫ#4l wGApwsjAV!1Qڑ꣪zLTQ}"0P=?#F=a P9=~O宪ӗ$ ~@y֣;((O5* c $h`w]LGGżNxΦNMٴ <@5@Y@8:%cPwNoc'SicV*qA2ι:AiRojA<>¯:^I}'@Z@ xi8H`( ̢[9z$f:+#~K @5am~;ʠYQ١9ۅTL 6f/ aAΊ$`gh _i/g1 ų4I(⧶+Rv0wĦc`GآchnAqgoR!ZtFngݠkRA+M9O+%Xa'L:Ӆ͐iѷJNԍᦰgzCtf&v2;[ˡ{ݟ@`x30,6n >`15D@'lV%HxњLHx($DLgp' @ٻ8?^|H,vDE'9XZrZ;Zh>6rm8T 1܋Lp-ġ6 2fKR\HeuTr@'k i[ҧ&'-1hʁ=(@&:A2v JMH4A;#m2`S6uPCWpc18SR(ccG:kYT~OLlGP=9VU=?⺫gԍkQ:G4cQQm+q4,`QR"dDѬe1+xG *?z支6-她 v*)eBH9f^3VSAF7&T&7xh//e]i{Zujo[(qckTh8AjT9AQ rk'0WU3(K#D~V87&`|fPߕ,7I 8Ms8;hH&dtaDmʼ냴ֆ#Ot.)8-gh4=Q@"(tQ8$bau Q1xrFU1ˆ'h$l"Y7֭Ma?߆ 9bs~hk 0zrAxn.*0a^A3&GL 3A^@;1;DjQ^Q$C&]sEQR՗$@`zkLʸc#g\| ;VnʂH\Lyv`͞יSRe4NmoV eHZ4PJ69i: ivcAVØ jt990ݣ`jiQkL<-^Ub8hLIOGvX. W:gtrIC5vNt@NӗC1Mt8ɏ[đ9f"ɐ{@44uf041s#NaT =t uNaF>ч>W:kBjN h/LHT @I-멩b~jjz"@ 3IVh:0QAj'lޙ{8n?9-$I]>g4#1)Xp#JJTlX1Rg..eR#$!hA_vJz}Cg/]x:gdv@i:ɧ8bzI~`u΂vWX'i`G7sQ4fa2t~zeT # pZ ƣ'08uʞѪ1#@_bb=[nVwDR\peiYa񶤉+ C`Jl:+w N :)JK L[]t4f0֫EqFW׻\ťRu09aҩJ 㺵f&-n">}n *;*GV ` 8怜\R4Vʙ-0L38mXqn=Z0 |`wUT҉'B-f{0 t-FRpQrŇOvVa&2@L;@;E'nvi tӂ!P#FV`; :pOH:׬ ܒZ*$#Phe˺z+C $~Z*N;Mh`t6]~=" J b AXV~N5u|O8zs>†֓maGG Tzf:"Vn\6ϩN[ tD\^",hfNfzjFq=2d_6 Q89>L r WU4B#z<T@D T0' #e-كTLzk[l SNX=QdZOxP?LFɈ#1"dGhc?&=ULvǨ֓}UaX$,2{5'^j.N;r %bvwknmu Pa_ Fb\^(ji}Zk$o*DQ9}j,i4&Cb$A o0`&6nϗ^_.i޹p?U5UdIbOit.. N#iplշټ{UIr>vƠWȱaUPd}k󮓙%P~J;oWžtR6l57{45f+h1wkXO@P5w\bOp1@vR$N&?5jb3ZtuL:@G>Y8I'# LVSl257׈V1SxkC]ꓝHLb+8TPk(뙨~5DuSM~3:Lz05$UOˆXN(eYbk#T*':εbMsm~>B A ϼݚdŲ]7hpϮ"k (HT`m*+ *#I^U-W$ ޣV@:$D&OŸ `ctOF)r*FW&?zd?H(Ԟ'.O4W'~N0VGhcL'0v6l p΋$Ll֜`l4Fi Q=kLA̯Hv҆pQqcRq"Ok |Lc0DoPkFJfEOWru)oUc0{8P{ s=4 ؖ x`qY)ZiݐNݭ:-o{ѫWU<e2xV x럈eXACB0yV+H8OfsJr-֡.}"~cK]DBd4rpQQ YaZ=dƻh&Gբ_.6 {{dhQT[08T>JfR[zE=UF8NT0=YVa8xh.+'h3[ONON=E޷qsyC)Үr4 0#Dq^Z[!HX]`p+ xTEe' {+DL@\:k tVPT!`Q"MA?#c=-Tz:}Hq*&j EAmAo;=#αS9aZz6' Yv#|iWmn* *x! {ݹn c̷l54{թr?)۲viaOv]rv jAfz-ӘS[qfhjW"%UTq0@=UD@vhcDz-C Dq,S!E@<(gP\c8i%vmF5j;1Z=tlMUٌX@aZR"p"IvcpA$t- S&( JeZ3w~ qS8{5>-5,Ȍ ym%NDDLA" HD[&qAؕdH|5d0=PtPN:18_֡5!Zcw M\Pxj ='W q׹VnrD&1hJ pMib[zqSۨQ׸#-;$98}lȏZ*0cgM u4V6<֎6ж AMn9%ۛreTaUjm7vΎ" .x?}kP2CJVz%"Ѝ{U r50JN@ʱPNU"e# +*GUF>2A6TVu: TWMGjm.cea_*Ntͦp+#egX}6u;z(XWEG*3 =C`$N lOux/\tisexvBV@'whu6d=.?=-ې MmXs{XOLxO# Rc3{N$ bJ br"X_k{w z,D3@~J8={*]Ӕ"m$ 4i{J31?EaF&LDQ3dJKJpc ֎V@aI"1446's(XFxݘSLW#A.)V_$ U.Ι}j OH@+GU.ńA-:tZ~bKgUA'wxgӪ>@5ug=]48 :haR&'j 6´Td {ռ< #Hac[6c8XR3$H9ګKu3EJ9oN (Rt(^Zpz=qeR13-`g  iU4H\s`#[GLEzTS *FV;:G]Gk0A^Uϴ-1Q  `lfh*s_ᮮ5=5뮪4&X MHsXDGU@15Z5U`z+>܇Mklm֔ݨ5ӅcV (VXЁCTk֑]C'r )Sy9>e5#KD o,NpQ@Td0᠀\|ubWæPCގj)V&r ƀ*շ7h!$+kQK)' Geڢ}呪::jSx`reIpgA5I3"a88hI7#wQèҶMl=OPZ#(rêE*GIl* "f$`* XzH1@cR6a?R(t6w?JPZWrTlh2:O\pjG Q:F5ciJN8W=uroHs5יMi@ɨ}3~1Y1# @ꝕuYw M 00;+K{y-'1hj\LEw5*dtmzFڐ+aRr*+'# & cJn<)dCg?1mz'Xtg40kN=5"# sΣ]HGX|ՍFLh‰RAA0j?Jxd!I^;:'iLG -RُPA tj!p2e[fQ!:?`D:@O:\LtW>>cz`3zS*f!GLO{2AдH'cT00aX#U p]C 6 9Op.E+)G5ӋG]c ;h?bF5nspʃ ~Z/l-tc 5$ A\R0RwکNö+ 8.1NtV;4PO qF"6][`SBF]|IdFH6WW*}Tm }uTE@*j&\ۮPbT@ٍ&IF*%6**& :(dh@FhuV*=r9죌FWUcVutDXc[zԪ$=$acZD-N&rX#`dD:K Ԝ-^]+R:xR,TaGrj aBgD~Z[oUyz"Q X6`+a}E~̷ GDz̍ ?yohQ.ڢ3:qJ$c<@ ]z׻B1=0k ' d{3G. 榎FxR3 -9x5*8u8DfE@SQ@kZCFdM#L#hXoIVWQ`fdG֫VېgWJjgb#[l#ĵ $N ֩[ir2~}_gҵa袆cd? & N+[8Q~i,FL(]q)@14F PRu`p,'faP16za ٩>^ qRhkR N@A^C<\\UQsm?@HqWpeVkεm5GIuVPp6W:vUAʤ.ͦ6;jiE`tUpU;G6Q׏* S cz3Ʊ iƈPj[:z*rL L"I֑@MX *{ZyzF8D =A߶̈dP`DθopJ9mumW*E-pC3+in8t^Ȍ[mk׮ʏPgHm#tm3ZAM\PAJW54OQÄ=*i*$6Ec31S;GvI_<@P}G+E-\&FP#`!&NqEތDV(:Q=T> 6cP? &@Dc i'L/]BѴe 7YT8'@[4mi41% 3зgc k3z'0B'f {pW+EOn@)gYi ;!T[ LDV8M vir4+jC}9{b흵uAHU07t׻RI?zDb?d2v' jxE.*I# !M ۘ&qȌoE/1d8=C`0c1ku-J:3сκk<:}EcǢj[k<Nc>tJ[dzv3&8F PUդඹzDbO{O?#F^e@ɮ&vPN#j6m$R1ǢhcpVɯ] g z N7 1`& dc!#e@9QM'8Pk h5?Mu Q$`2ӇM3285UG[G;,2UĈ=$ǬQ\ Cuhi'^:Z *wgk3c w¾ɅSFc2)H`1{եО ֣OSm`ճ'#00Z@0Ԍq!Z!Ma;Kf ,cL[9sh20r؃lRP'rʕfŲ-HpsP{DA#gLr@{5vTM(LuN;xj]Len0pV:OrcR8muȂh8`XK`f5G mC[m8T/l`@5ELe#mHRʹjmL}ZS4$i&m56 pM28)# 3@ãlV0OUH8Gh1F##hԌJhF)q#`EA `z Pg#Q;z=*3R$V&g:ƣ_ #:"hsbz<5>* Fs $Zؖc몱AmْRN'2}3@z1+!P2鮯FTa9&EaBY'<15XGejԓ SXcQ="D @&E15:5l"1¤ԓZ-p$ɠĝfbvX;`# GHRUz P:|L8ܠLiu=UH,Kиi j#쉦C![ą1IˡDGEH8d#5|U @=ƃcک\ "+(#5v6]dJ`%A"5)Z?IqF z)FZHGUp v̨io /d{4 6;Tvm)M;8"'=P#P$1=xxP- j@#QMJQo0g3ZG֨WڮGxHKQ^8)nUg'VreX[0u5;8H4g=<-DDu+xzpO'︽6'ڡle ~ʺ 5Ga_5Lc@d(Iꮀ5uWMDAǮx=*SP(44ߐTl9ԃ95c@3GRDH:"e1dgQCAh##: #c=_B*Yg@"Ñdn!rM\W$Èi His:jR T#ԪOL0-0 zE4J& ih6t!9 ЫZg2j*PȠ+}l$ڠkDcǵPHȍiQzz*qpdY"#f:68a >wEon2iƆ2j(^Bu :..@J3z[:V^( Iֳ@Xg0Fh Hm9" f1[FU#*\zTcPGgj4 6Rϼh!\BY;ڰ1Q Cqs?=u:Sڡ>讚 ΀T:`wיp6[](w[q/"̼umۣ00C#EYvjlw=W 8N&Hƣ6lQAffhR3B03E I9'"j5TDg@γg*fI=uf(bsEEMR+Agh"8 0I014-.{"f:?Qh`Njݵ:v-j DOE+4 (z{io3sy3bO" {[($e,k^mN8kXNݦc xZ08Elj2GEA˼vhcLt*3gNN:F$ Ow&cDR4} 5dN6cbV1֒Qڨ#taQzU&5Qpw^؍D p6Վ5wb$01Z!\#0}OJwj^c i7;У< > (L` Q\mhdR d;R;N j*S_[ڷtTt슀#A@ z9NpM+n;U]d+ۀ1"?]TӶhZXIDOHʁ\FtX.F4OcuP]/nT5EԟN5g*.;@AV?tu+XF4$QKV$@Lu.$T*1'3(ĻqV`3[1S(_Z{<@x* rhXC}KiAOd0l&mF+``"~%; ԜOIWEQX]DX 3SP5ꮊ T U*dԍ e]$a_($@RN":mu?MFuGea1]T VTek*GMhc"Τm;vf:@Km?bUF-[P }KVɝ%0=$Hl]d{Éqwч"AB0QMC" Qqմ2zc лlN/THAy(m֊Yֆ~i^b^ MGS]]5AP)wFdfШꍟ*DuamgagG:&?5XƄg5t dRP*=3KvP }Uq^)hb Dz VejiOSGMDB顥*%N;MU^0+]rD.*Sl zޠGKqPlh ;KVCiPA8tnh9E̮XkQQPְ: g?~`^c;͢RNz}Je;I\_fe7Ve.Tl} g*9Of ;4(9(1:v{TRa̝IգF 3[Da1+]E)t&gTt^*Pd*EIEyb!-8qj.w4Z֤b>kF üm(2b$0Vm옢`LLicݩ$2*L5TR ̊î+\ʽ`ۘ艃A':;:!:$$;zh518M1  aVq(FɠZڰ>&j@Hƾ "H5d& N`t=嶲c)å6SS,W"@4tT {'I*@EKZQbV,Ǫ+v'ihjD*7'tdTwՍ@,`brul\,Ź |ݚ bA`tz4s=iN8@OM  Jo ԋC 3SRUNʍ%~&oQQYٝN]* ÇMb0;*FG* Zg*dtzԪsDjGN +8PI >j0;)IE1Rx84+ K):t6lŷOM "$9$ >>Yaꐷ﹪ B>(O&v;gڤ #klF"I&]H&qj$`*  @l#ڢWH=3hLveDlMa'VDj\3jmChBN9cIp%Vq#{U ZN3: \IN;sOY~uƚ{N=}5l'Ja0"f'/TDOOCQ2DEg{l sVG]+9ŽZnEZD V33Z$[mQg g[ Mp|h墷O 1,s0 ^E;z@0RiT`gAT8Aei3#U^Ճ0 v*vF3F1쮈Ϯ v@tPtqޯ6 eԍ* L@-0a+4t ~H"MϫmEz[٠Rz^+ WsEskzvO[k0ŚhvqN o^#?VXjVzݣ棴[ƒ 1X&tp:̓CEc"j`S|v\U~?-zuYʳATK6&&]$Ϣf&5o]mq *!pjS*(%D(XQ5&F>[ZV DˣX ݜ^ޚ8G#*=`GVrW1e.Y$1^ D)T)un6~JDj:;`ḷn-CNq%'utV#*{TJ*w;{_)n9 ߢq@'jQS5h=ڐd̑P%cg_yibtmk=i‚F}᢬(g`aZG}l'ݤUXVC`E2&#dVj~[{ڭ[((̑8֕;eVe$8iR%r`LG0V`V ɬA6M} @I8_ l T\@a+$>) 5(J١l)~ri+ 7 A]M`AȩSoxQ7_# t.if) pbæP6Tb{$jCOٞf(1%Tc\qlcjs9H>jsUP6=4;.\m*R ;i#KrᓦZ°U"G]al##A}u@le(ghYGV$P?%Lz+'ttTbcP#jIag@1qT0>ưˮ+iQ`ʀLW]IQu*Ln|48MҸtM|BMPf0:*\D ߤE< EK]^Sl +NFnS:,*zq c*Q1Gȥ=4D`v}4F_ݪJ%>0^XP!@I8Z$:₳f2Ab=4{g^& ;S 'ޠ%Tdj7^IĶ/ԮO0TBo\nyzb n{ҷ`0m:kBmA*ÃJ尷Qh˪pK14, k38C@'NRVӨIG`> #RGP`BD}V\dnjd,IZ @Ī˕{oV7u4U^qң!ֲpLW . ;L$$H5 ` ݭB 98`p#5Jψ B-DZn.y4@;#gG Ʒc1᧨tPp x̟,HƊwjnqlQ$O&&hN= jM8nZZ9Z6}@b>LA=yT+*M!-۟ի7|'3jC"_FڕQ15N*H]HڠT/(iid|]. cכ2ßA۔Qϑ.67@mTZ8EI=utЯUbjk՝@h3g]DaR}T: pVtIk E@zS18Vq5 $S:Gw bc(_0j]j/͉^`u8 7N$d(=xZ:I]4≡qp1M',(ɍ@jb_l΅5 OUIP7m5EJZ@B1r"5$c4We0GuйAv@ v'ӏ\+-23;ZbP72"pև;A뢍0,Xޮh`mEyhLHk9]futԠU <'+D (Ƅ;^Խ XBF&8'ozD+Bи7xݥ|@#USGVa 7z*#y݈-zp 1)O+t%KAodVG(g1X2ZZs&"`P0epQc39Ԭs05e]i-$e$eyA[{An 0>*A;zi' ?/xi{lu9MԌ/] '[ZNNkqGQ6*LD@*1"{ &v WnPp51ɪGFY 4m6b -1Q܌+ʸ0P*ţ'S$@ǪY1VGBٛ0:* `06Z$kNyQ5ղz5OUEMl8Q +:@6Sꡤ꣤~]G',kuuׯ* d+e29֭?|U$F]Bce/8h0xeKfŠMӔcG\f%u5 wJhlhFtFʹTvQ@p0[1tVzjvz1Qs:DP^Vx1h ȩq ;q3?EiM=գfW"Z pN8kLd ɥxf @\en{p4)zqNݢ;='?B0F8jif0hhb&1rA@ f(9b=W@:rmB6Fŵ8%@({Yp3 4vznݩ3l1Rᤌ@6 'F[ԥ4bOH=Pz3fI'J/H'bAe"N&UZ3'PO&a}!c9cPK"NC5 {4HDQMikR WF=uMoJ6p٭Hz%:j2'hp+;K=dְiFU8y`?}TN`}8Aޢ1ݠ"3'* 7LEl`y^ ?ՠƣ'4=fXFچ>*#֥1CWGUtܪgWX 2识7rV^ L<4'%ވW@cjSb;pZdZ}3f=tLuW45$R?3#8*h;&vtTf+jŽ6Q_E=fTQ;h[H;zjH528a{To<klB펊cSDu7.):vQ[`NަqFFJ*-z15]AF],p 6ߒ#֗T/hE PF@ט0bʄ2ߒs&C1@֬t-h0 2h9;Zf\Fމ4O4Tb& aDم`좬dE@Z:8k[`ULƮ$S |T hjŐiQ=Ur?uQ$ANcFdokk NyhWc"t+g %U@t&`ҰC&\טH|MʀYND?V1,]CyswhDl3VbwôUta VcMBs2x(տڣpaV9-G \f`Oznj J:)+A]T ( Y\f\c509ɏp?VE#叆@L#J2 ˆu,`=TFފljH0$NSm*kc)5'Xr-DdGݮ(; "+D螘 W &+Fʂ`>xmT+0ӽRq`Fr]' @j,[t qLQG0 5-Gg3 5)8F$}jEP֭6YYi+WƸ-@~]ŭ iXZ\L~?ZeZFԂ(}`#jgT 2GMьZuЏbފ*6Q'!D 2P9zPnj-rh[A& 9V κThEv.&UQ=* pSwH 3%6(kD"PXATm/5q6Ei+KaH5HZѝff% iRC ր!ziZ܇@1"$^nry=uIӱVު'0LP fF;TΦ6VZLD@5.r2f>⢅Xw]G"3v>MbIOZԠcAƊcaX]Ưj`̯@/{^^zv=u, Gf ejlʛcn?mYA1U~]8)z{U I8u}Z8=&r@^8c@S[ ?ZՋZpxt  P,cQjaN I{tW" C#mI#J6 #aᖵ 0c:!$@13[27T2VA6M[p?>j>`kI8l5+[kc(2#WM1nL8Z\fh] Gj;R>kbA8eg|k4G0- e6]rPt.7Q^I͏O5%F' 2"_?T _fMHFfM|_]5Q2:*TqQDlEf}U'iʄSegR`:!§5(18Nا"4aMFdqDu|@EB?V 4-1N+R=TqDZN]Uiv6ecs50NEW "2486Sf,'A8/ڌ 21h,Eh$qZl \n:Fq@=hPB()۲iZlbH:(͵03)UV3jmڥ`w6 n0Y_Al$Hµ2A*ּ.:ݧiih)bGG1W,+ o*ꪵ0{ZfC7 m7|TRJxԨe\%U[sfݸ "0( K69kV 7wPڠF:`E`{%URMs#I(6э,:{:iC5 NQFQ(ۧC FA%XMVJñ'֢LrI(hR k-1LI¤$H˼dqrVӫ*伝*@`kD`=b|UC>7ͣFa~u˞1V@E>=+W͏UFݴݳ- ڧ+ʤN(y룠 $P*[@UW,[9*jY-F+RT>{My!puvZSEn5c[+Vv2$g P =P`Ai 8ҡ P$m𷂼)j:zVI$Pg2ەb1>*0\5ݝ?=LFGぢ"sS5ꡪ(@ LQC +D sMiP+6 @z&vP뮰*tRŜ֤8h&XRu5(hTQm>z02ޢoM1U!GRm 4Z($ f>)<N$Ae`s뢡th+ډ,Jp,`LM &00" b_f'grGkT FV dmMAڀLUs{ "ɡ9T˲o4CN*3잯ko];`P]0NDK2,2ȝvBO -7TH ar" ILkIqVf!۵w1BJ WyۍF:\]W_!*ɍ>&]݄w{u!T -{n{D GI٨H!1Vih}Bt⡂$L3cH =N6M0Z$U\#>YNC'8Vl^ FሞlPq2wh $~  6P chf6l4]qkʹJ bTd=L,01jL vׯ*U5X(EM=Xoà IݩkRvLbvmźb1ԽѻIW]?5BoSr Q^eV/z UaNޣ}G˜?=jzW#U ]Ϡ#DNtC, 6sJ0g ZWp DqTI)XG-m(|4 $Zϵ(i DV1כ˺5Pjzq, tGAĈԷ:>sSRbZi6wFgۭ@cP$a~jsA+$eR Gan[BUs4o.=x3 _ltC:7\dMtAo. EjGNt,q&]QmC4&Wq'2sbh$w1µF,2Ec N" 4H=4/ZԬLf(lCb=vIvb&LvVxFޠzi`E8Ϊ;N~-蠤n (hlG Q|7,PU0ǣd?G@)hE Gk}rqF S>]@Tw_rXӍoc; f4\7@h{KvPZ`0+U9΍Uų ȦZ$ t6Q$OyέE~`jV4P$8X` ˥ .aNCM Gŕ>iҡM:)n ӳڠXp#J+e ☯)q{mN~ w kd8xZ/~QGV'|4YxruAil6] o ' D4 vt[D#1E.n4HoRr/jE hU7lIĵ/E.aP/S0P'g /md1R [M~3 03-PlN;2*9ar\ 5 tΣ-A+ Dz9cjTothnt8`' zh&5a'V!`gaEj+,3B KgGIs}1ܣ?1h4tTAQ\yn~aZ:|ʱꓮΦ`T0gȝCʹ1'!^c8SGQuIOzL;(=W0V:hX mXN!- +Y0NPi) GAWwUtrV']B2f=Ur3ElkR>8MͿMI>4NS.|إk-l⨳ ه9qqF3XzhшRNpLm M2vN$E|ETMi\ƥ1$42˧*pvmu?_ 4.㍢bf8d6 a cE[`?Vi7uy\)b4*6w{Oe9 (tϺ+K6% eOl`(u9/(VYwuVm b"ޤFtH&GѽZXn$b) $Gu2+c;A#YnbD09H.@:cP@&ݯ޳uE31G{GR-mWpPl # i5SݫLҿ1QCĸ>W}^5&GAP 7bA;JxEr"jf :( c޳ZBa>z!@ s&V>z2r9m(zpT֓23#h9m 4USHW\횅SnbhuPWCN$S!]t07Ap*Nt~aX׉a `3+@]L;JMNIJ6/z:+@0QTn&0>hLo,uPANZ Al 80ɆlAҀm7]WP_ACP'Tz>_AێRݷp"kx`ZKomPv o@2N>5 Ж<*|㙢1#<Ԡzk]ѐq504U6D;ⴈ,?%5^cӝ5qalb@[K&]bqQR9;NL BE:TpNjE~`-(**Ӫd2I}EVwt,i3Uu:?~yrX.p8@ҧ5r]h 1Bwv6ݥ@cc-wgAmٺERT2nvS-$8QRdabIlI Zq#TƋOэ&Pӆf3đ` g(!*֣Uݞ^\ ,I)})hqLgF qs`~[Hu;pt`uu.[ C $~ Q;h28EAcԒA,2"Ei*'dZm6@U⯇T O|댛 WaU/2m9ȃ[@fbp]@"ja19ՌjHt(ը1c׀jz}?=y}#tðt.-5N+Qđ5 +(-i'rח[Bvbںwt oz(ܒn8q Hh5rbLq'h )#R0zA95OHgBÅt@z $ol1;%;KhY%)LFuLEcXliu)26xD}lW òv$Oi?WE21bGEH26P&GFnZRq k==_tGiR sִ 'Q(q۝j"GMk :U$ UB,ddDFJ C.M+VVpW%VJ}kVos..S${Ik3_8BH\ cU u-i?v2H%.(UEDq?Bꒇ zia$ pn1D6}^'x]ԁZzL*b:Ii>cBa樋?k\bNj kU[H =5X\ *W3N|@Tڦ0N^bNRi` Bέ*Hj.;F1ɾ< ںMރ1CPdMڨyϦd[?gU e̦Fꬣf2ƄJ飤9ژw]Il6,P0':#H''Y&|Uەq1j\j3QF6(2 >*^qѷrܭQ`wVAb:Ee=N; SUADD Z\9|ڌ*&M9M5c:vdVDѵF@QXuV]> qkH5'3^ppA4ٲ+ZEo/PeGQ!M%֘qU+2~n%@ fI>YhƷ_ͳ|TtH@ aϦE+䦰35h|RNGv֬C 0hX1_ٌ}5YOA8 R13=wU̽ڡr}[^.1p qZXSn٧enGE;>ˢ{5%̞.r #(δg!LbRNe[1^;':G§aD Xꫭsm$`@bh\P/^¾}R$Fڡ$UNdPF4l(ǧ9*zzjQPT++י홭9rh*ơ8ז-$jӌ{ 9וs ɨ{ E 'Vs^li;#X쏟U+Haμ?Ԡ˅‹:~j d=[ _ye*x RhncIxbjQҞ]tq&+Mֆk SB]'# Kv=.TdE=E%ͤ-stLU4׀ݶĘCR|#JHU쁔VIkW`ӖKy֛WìrxTˎGE5ieZ9efQ8]ԭWQ ~̙:eȀ& Pf3'a1] ~jkMt7CHU¼"Au$d'ߡqvn# Γ3Wu>rT FÈjfBPySO:mZљ;'"'u]4V"'OIS+l1[?lKԦK<(~:7YB-ZoS9V7[,vQ7pa$Uzk$0EE X ?']>!uҾ:g>h_#e}fvܨqEε =iMg=PH2b٘4+ Oi3eX [$t|1$ttU]$$bv'MIBhe`%F2qF*4'I8tO.6#?D5 p0Y,'ک[m&+*["#TdT2 kN@,\8G&q5.MIBGC@mW$1l1@rHđZ6@͋S-33ZXN8E29u?s7ѐZL*W4>*I ;e4 LPH`H c-$[6߸3 0xgNam>]d߹‘:}WK|cq9-%`1On5hԸȡqtgV NnqrCKp6C$rf;q9<1n"RƠvvi6A&d=L uQ@́D.{kHi ޣKi84 ,@9詸r 0v#b3<˒آ#^SuOe5Zkjjs1m|t/*] %[."6>l[ u5yTxiRiUKfۭڹKfE0cXLm*ǵMr. -ucb';i7N@XY Oݪir@f}tؒD0x^DF=ye;{&3TZfp*uٕ*{h"`lF(Z#3 V1!L {^%{ `t M N'QNT7I*'<#jhc t?Ey'k{{oUC0Kr $[tJY[`> K hoZ8bpT^T[zc(Aׄ =T8lH8 X@Fi8Vj40^U3i:(H9*`f&+[I b!i TT3N@r# =t: Eyw061\1­By"LUh+1?Dm'vätyjEg@e8t Г9-H^1= \R$z&k6 @B;qLB4o0%{]< x('7`el`ګrU"deMei?Vl 銻w&݅c4,[bp%- vF':,Ql Ϧ(Ic(۰q$bz(\ t ,KleR03JT(@ i-' (ţ:יbH=tKlI ^~$BJn&>Zm[!2 Imc5I {7^膺C[~-b/0G2"lKG{inku S] dc=`r=Tdf?W%aX ƭKP_T4Zp~j|0V:2D3`0bXjΆ2:Fu J)ՋL!j`z[~\ 7iKʼt}4jѹLtlƵ=Fs=ݨ7~yӮS =tm3'2+JMCUaT:Pۯkɶþ[u^NLIM.uͮVw-wZVжڢos_oݿlڼRă Qn[j HJe;¦4Ţt{UI{'gCmĵ,I̮)F;^]D bݴYR`HޠdP #څieHWN9ձogח7d~i6Mbr[eV`@ XX$'ՖIaLzh鍕[AVAjq 1UrLf)|]$ɠQ AWjޮ;(Ōz*kDzNꓗPARu?M<*GE JYSh[ O@rNqƒ.L=tvPcZ+6ԂԨ8.D @vɶݥBdOy(HĴDDN'B;h(m``(?0gWJudVo^"'@o!E>*m+6k2yj08Տ4l0ĹlPwZy6 (ς jkUT&jD2 @&cUؗ#@@mAL4e%VysLC1YհiR:ynb},p+KMͼN¼.X|t5Ԗ,QBHF(* [<wIë,ٰ)i0Hμ0‘*@i6^Z4.Z]'QsoFݢXdUʉh0'**}K<3օlA4y6uP#YIPXI5C)^ًeXQ۰M9nM:a+) ,heN=5 x=k#c$M[e8g֗‹H d5Li鍕1lS\eBPkE iҚ#IdI f GAj"p#.BQ٫. r7(!DLC+>:Bq2skL<-O2 )d1ō& ҧgQ}b۞sݥm7YA`=5m̷hD![mnvAܺ ۰۶y K~s7n;+N/eV35ygTr4o LEyψB.=4o(<,uRX!D]NY@|F9}m]6RYPt z%xO Ȉi$~H=F+f4R'ey5,2Q=Α"m&@_!Ywd,S@# #bz:k 2>AƴXv1f5=l@(Y 8P*7swd &6Nkjd*:ioB+@?K9[ILlB&#`5k9tib0oHңlB/{[PO-N"JVv%gBEUvtգP0蛄<(n4=Iʂ؜qf(1$zV6kcV¿V݄}tD,E˫uOwݭ q e 6T|K/gBŵ%6S[Gac٤[%mNS*~xW$+0m4H GG5Wm!i؀M;5nЌN'DW9^O-"pT" ᤺U'ss GJo3 #1Ve觳nHi\Q"p. م3#ݶQkK ^_4 ^q¾mt<=GZOͽLPs Kj ^V!HaՕ3@JȤ3:L%"A?@m}®u :K;  {o:Mm/ iJ_2;@01lqOtv[fogU}|=*Ulng?i6qf\uRV9.ȭs0 q5-C-E,Ҫ \ӳnئ;ˎ2!Nk -OqŸ4"?xAװ_UA&NRh˜U#6 ӉWiN6i^."zv}8[(H;ƙ[\I[x~n-gk a#١od`0kE.#[&d6")*Ƒu\؆JvF=t^)ljw$ 8[>è;rVlkF&vNTSBK{ߧᓜeAS|V%`{M 33Vr[StR H,s=9L5Pi⑨QmBiNL#i?5[:롐ǪAloW][qI2h,"pLhE]%$(bȑ;wp*N /T(U]Fv`GUĂL8N/V48}#x(QD4IH1'3LKg4dc)!yk35 $j4o-{i,䷚s+ᥲM;@.y>\ھ0l1(Vɜ5"4o*+I2sTETNjm=CIyS ~eF|k=4YI`-b$Hݟ_~ュMKNC2OuiVb]2̴tLjҸ G e_SIrvR\ޘ vR|ƄENn\v5ԍB궆" Q@5:N ٤!#R`-^XMdžqsjT=|V4"wF"`g6$bLxZdcppo^Qwbm`-5n[V3Jn*|Tܿ((^ {VP$"o49 qiwm:uS#`4fppѹ}E-g,;DUqt3cޭV#ݭEÍ r&]ZmeD3(wyDt~ių nPxN&j;b!N^o!jTkodݺn"ӝA!IC#٧ Gן~ln)_0JQfNȠ!m 4@ KP0]}CS)G\b409<\;&ڼֆgUp3⠚&}]T"=B9 ziFhGTZf #)P VCIVnc2K=FXU- N;fdqP,xD" S$ #m2>fǽZ,.0WAz\;A'Zw"NZi3`vkG.P\i^ 7/ Orݕ kc4YC4v:aA<: 8CE5֤b$i9pEÄ+~٭q8K3`On I'`,OaɐfM4%ƤxU)lK`ǀXvdkc5h8Ϯjna.F84Jp8fɧaW)jX;) 흑2-k=0 êJ0FD8& @$=gVČF$AzقEk;ڤ s 6 cb4-[bJkԠ[^AoU$9*XFf,ZY-7K[X o`șmpxb "YՉ`?k̹oL ^ne D`'zH' -revp*z;ԭ M/W`aj<ˍMx̍կ41>\U2xe-%.nL OZ.n2KTm&ҍ21N@8/] 8 jMs%S3Ig{oD܌U@v3q1]zan:1AZmn!R[mvk-KyKr1q*-sLlX;n֟/ Ql;1S Ic4+R @wM<\["T-6H`aa-&8H NtoctUZ>%vCR4Ι&p0Q(@Vs?fdZSN^0 8ҫPD~ 6p?r0&+ˀ[5 `l$ ?z]b>3aAh >#`),xSrd)4|g|b"Y )Mhkڭ7g:4 mTZfs5jXUp VYxlc[WR)u!@h;+].Bk{|>B}PZQUPwD:EJ!2iu,$c38 #!aM: 8"|ߤPBqz1NsR 29V56])փE ' hz*0 F@C P|Ft919e N0b#j N˫h):1TM wC 6{:j1ԥ- =h%YgrkbZc]Ny*9Q1%nbZ@mmKZ`;N*ޙͫڳ;w} UHzԤjRN^! ӕ%{Gb,$Q1-k'~z .I9C Bd';td·ovHC4{3={FPd|W`SȦ#,q1oQ@JbNMP ZWyvh\?U}TܟPĚfYQUufp`W睴5;!r[hk는:biu9bFb2cK&GM5:I$[l\ dFtIgLgf`^ r$K̨=&rmAFȦ˂<ܺc'e2[э6:؞|f6Q |13j(I1G-=.K~[[P,?jkwƥ1U34d5U$B yc'm\KmXȦ7eާ frx$&pRJZ2' wZk|A )r?r^^!B.t)?VJoQlfi{>.W[BH_kAZ^k,-Y"gk_\vΦ4/VéNl+3;b~ד~MZX-׳Q&_<ҼۃWfmw4o8&cO/fI֐Ucj-s hrƋ1;ձ(^vURh۳qGGZK,$PVR ]-QlcEk6\Iq8E+\eHQ"AGE+1QNnӥ[H7TFءr~)Ӽ&s;I0@}4yq::^* r\H+lXNkAF8ZmkUd"p‚!bFq}:AQժn*Vۖhqi-1Hѿly͵;;ؐpƋd/G^eIZ>ϳNm@+XYfTD `: 6xj&EmC0uUΒ1*_HF7wFi-ӍVnye,`;sayX2:EŷèOW5z-p>Q<6UvHzy&>-3ڴV vA˸z6Mm"_HrS(j BjIq6ʵV;NjO_PaMG +Y/\&-`H} ] =nb`.}{h\ 13&; ݢWFޕ@nm;T"+`QGK@&A뢽AJFx[q.Xޭ_? fzAc;hY&B'/Mqy*~%@//p8l" .bb ɥ24m+Y,:gݫdLF920kRcݦo.4mC8$PGanz[[fEE\kt* lb:bB))pM9*oܡq>wCtxuVm6N& {Skj@lυm/ H}!B[$%by,4̼,|ٷMwuy%~55n\sڌxj9Y Y1ncr]?K6労k=6.^؜gHk_w ;MIj2!^ӻ_AEwڕ6'WLh;~N|am;>va=@/`Vջe HyW%' (hPnijˠ5C$=')mI=ZS-G[chS\rfu9t/ŦEka=h NV1 q,yc^%h?9Y^SP 9_P~}ZC5;6W>V[@ kboӱnQXTjRE#^t/VTOZRu9%Lj uP,wknm,Dž7[epAl]R`IԗYխlw l5*+5ŴXA7bKV;uIk>ee#WmḰZ$@f2 '٧Ru(m!G -g({W|֍uW&ڌՆ<j !.=GzY#it m}N'>Ahj7%i4tKdb…lIp-2TtRޘ#k>vߛh PTDNBCr}W]u !uFmkV{Z{=RC}ݴ~juӷ, V?ܹn(ytTSV]IBχVn7-YD'f-[djj=ջ. E,t 4h71f4[Jv)z=-%M^Օ#I: rz[fAջKrm #V-+n"sIŊT=e\wSҖ`nՆ\7]q )4ئF]%MjA6NA4/rm뭠>;+G,!*eui+A<-] cfr<$okٖIrHdT07 1ҒZ @TvkR!,Qt՛):}Fъh1LgFahneEȍ;&;u"H⫊.ZC0xv[QvQKcXQ5ܲ훧Hpj70ٍ* /26,i:觷et˄fHfbXe{u\RpE_akJz}$vHEsHق&QjǙo6g-8p#y0@'4U4{~ mZRLI>+6^эLq2r~tzFѺg3SV.Ajԉo݉1svc 1o QM~k'm Xk,rǗ['AQ+Υ'=+ʸ4\=vH4R!9j^). "XDj a΁+GGe^ĊJ X]eӫnZ!vV[7qի<Z8 t--Y{~p#i;f>e"~m4UOHi[i"6ug$% mOOykο-Ϳ١ ?H?weԝY*aqBGf놺@j۔\50Z`Q'3)XT/Aţrm'Auh?v[Vë\.qvṿg㶫f-Yva 2'O5LyuZHi|V GczݺȨXcrT830,h}i\qWSLىn߶ܩ]Wxnf SpKKM;c--`vDKD^޸3٥BAwl Kzq`i:y&4W|E K5Rp +l]5@0Cufn6hǷ1~ 6lEFKv nFOkUr.e?Yt ڲBke:k|Ӌyyd+[v겼+hk>y ǧI:>2$ґIa\Jr﷭~ u5vKnT!$Mo&@7)q=SHgn}gv H}WZS K1*HK-5] ]'e;Ԭ[=",۹qFtql&ٹ/ Yv'id]ڣzmX@q$ꫬ~[k=sp*ڤvmmF}mbUt$$(WO"A$ۭ\ip1v wm/n}WI`ӡwtvxBZu#[coPny-ߍ2§&|gTΜzLIqMһt.d@E!\4d Ϯ?U _EfgDב7KrպocZfJj7ĂwءsHfu>~"zVhc[5-r{6*cE! 8}֜ap`:Zn4x$dž 7WnB5p.3NRXLi5fŘLhz^Ln%{F0*2\j[6ۄ~WՕ[oy@k4|oIunGr AUYw'VǼ6-!k{#Ui íT2jK_ `oc'RaՈ$ˋ٫\ˏdGb,?V4Y,} ³?tn?Ģq"{”"” +mm1%bԒu.n-]Y%R%Z)LHR#rޯۮv(uI%6 qM A;_'K2iP7HsjejŤjޛvgi;k(YXڜFQޯlq'PL<)-ܺ*Snjp&A]>f`uR"uSjLk#A]U\hH!S\ aN: f)e[C@I0@ɠһdP6(Ҙop9N׿q z9-IFijZL }jE+bg#DPb3΍l-LTxjA-snHte)-ݽmYs* ѹ|;B[dI@XN~\w'}(Eb'ڠM3ݺrXVm)U:~fΦ[Ek2X[b&d>w[ot1с@aR1J6W% ;zbyY\5!tv)^mp1fovijfLY E {U!0M]SK8`{MMpK6`{Z[vǞ'MYP5(`K?ow^^۹p рē ZPY:$6iZkV8.n]-ҚmZi4gZk\.ztU۱Y/=Mo) piuF,իvFk *] k\p>U?·ܤ&ݭ]/r~#穸/%yao*GnkGKyl(??fb'ZҕW$ݽOu<~9xqf6lYN媷K1yr݄[|Zaem/mnnRt-k HXy޸JʾkI  0;5g 'Įndj,[ʍa 9zPҌcd Kڈ8^m vU@b5Nz]+U3"XIwL  @|joh\AOuUU%oo-#n7bb5˖wwBۡ?1qA8wQq=׫r"dGr1js 'W[d, H9i $ hؽڷi2n79&+h|Vۍ6y~RѲ{w\n|hX4OkOgQ'%{KJFGE0"WI4l)ӎ'ek v\i31j]*NeQFr"@=u@.F_wH/[pR0;4igE6'WHx'1Q|b,in uثh7N{[YaĠ ￳uYf!n.GY^ vYwQ d&ŋ6Uܜ>;lk;\~5ӹ.S fӉPOa[IoHP"Ii< 密_)(!U{B#M:IS qjZbQղ5&WsN[kS[6mq1èվ_*Sv;گl6zn;kLn^@ p-/,%P6 c%#O4rڋ0u JѻWj[[B[[z}]XZ Nq\ tW,scET{WyPZfwuе(]k~Cna$٫V96sMwnqqL< ޫv) «CG{$$XrcN=+*joA9 M\>Ѭ_frSiB7գw-F<"ZEoaEܒSLf@=fk tWnˬ [Hڞ uT1 Wu7{U||}'v  ržР'{[K.URĶ'9s۠A9 zG5ŀPt B6P-T^4GZ3G¢݋qyӷOiu^N7.#%[0Oի?֭G 7՛vߛvݕ<_IwI[)n׆73[̒hZFL6/}YaiF[ۤ">/:\n{13+:b"tWWB]\W(ZԺZ%d\iطZyKޅe"XWOzZANJDS &VŻէoDTA^S8>-޼+η(]9ZR$)t pcߧ['<{u1zŬʂ|_O\iIwC1ISX) U"3y/6HU7~݌Žثn@YH|,õL{A/Pԗ-"Axbzz+ON j|¼NfQH&bIZ="ݣ ~g[j-߻Lyn;8)Ez5VRxanݱSco5Ƥ۴Yۇ]WvNJ^%cJ[sfP쥯bi 㳩g Tcˀ1Giʶܚ@QN-h g3+F3Rӏfmʫ0$*w_?q`l&TM!5^=/x$QV8ի |֛ ũr[zӝ {FsMNۚ>%ιkE& կ-5CjMyd +7h߻y$nՅ(?b~%{弸P57q]ܻ@8iK\D3xV2sqԝW_Ou ꡠܳ @.bkvZ%=OoXSnj6<`ub\sjك$SFF)PVu=*XXzXC+e3۹:Sq\DƦe`7NdcWE屋CGuQ ORb ԭiiąWwjT0/N^ͻt9~\|Da}я Fr2AдnE=njߺvIm&^0[`uӭfz쑵ɲeK$3ey?$@$jOfG`.l-\Qp9yjmnיuRXŵvhP.@724Q[:TJ6[HFm@aG{V HA3ŝZKZ.ޣ3:@I#sH.TLl\5]y^HSJmi̍Z)ɫl~KW9S:s *ʸbO ^v-ym1kFuIg'mV:ut%Y[_3pgRJܿiڰWnV7,[ԮlrZ@Ggn[ C^e:`m'oʂt%QǸ]S):Tؠ̦&݀`*.f(ڽ%KCe  0hi@isq-vV9}aoF8.9ќ gLˌ x* tѰѠ$co?QyWBUm3sWASJY7u3Ż㦽qXJcxQh]y|1pF?QN+G3Zς<jэ n~&źKibIFջ69nVUPoW_O/Ȍ[sUoPI!T:q^r iSui]|Qmtaqx Hk ^[p#ililZޢ[7Mڵv3NV?t;4FN9l|Rh3t v[W /5WůwyEG8|6*iYu'z6ƾ'P*3*Kg;7j[CiqTςū݊\`c̵Gرm*nvW.aYq]nn2n^ UG ZB[@=6.kU^]456d/tkc oG@mWO,bcN{4MWpla%y'Vq|iC2ƅ1gu,ۧidӭ<ͤmյR_nbXN:~^ڃAT)@l ݛu>M1^a13Bޙ\k [ëE/uR/?e8F^yD7 3+qnwp]Y]*Y^am!ԫ.{)R޺>3ңT6݇ѿ̨{w,8Ѻ/viO]:La«E97:j-=~ZZgP֊řVtZf vԜ58SR1E;qmJkWmrݵAcj[M0=o\Cdqvmͱ~I[vy-o#Q4g,̱'Sj=49J#[x'㥺W@LjZ^h,q`/Jy[yڍM*)]F{T) &=z8R}ݕWýJ\Y,}sE-fu'RLq覺6'd+U% cvU{׹6.'rkdTFEH+Pm^6 0 e;w<>>RKRmj-1faZ_Q,4JIkN̼\-P&?ߗg@/ [l#CM8b5 eGkk7n}-\HjnTmxwzr{Mo孋|l!Fn85#+Qo˷dc3D]w/8ңA'>on&'WP¬K`#EK03 -=LrvĚ,mj` wVqo &w6)Q*cV7>dJYa Bϊy* bāvrT !Dj~b1;j[@(\ SnT3rnͶGɶ,bOĽ?̺զ nL'y2HۺV&㿓jų-^}^e;iVGt*۫*6hl N`j׫MDҺ>ū<)eʲE10rHosWG/lcs[VZ4ql,7r]0]"I2].Mvpґâk{V5k[&V7VM6 d` IWŮ|pА>_ݥ,I==3[Rd0d}҄֐K.96i~8 ` 3mR>ɰ`z-7֞yIr]4 d')i/޸h2MyQֱ*4#Z`|v իyv1Nj]nu^\>}<>2kͨA*;b >riLֆTQ3bM3M e)'aͲkAiIzjB.]r1PKR) I]/qP,U뗵ϰ-wTsF'BVu0-BҠݷF݀T XɥÙfH,Mi5a˂`1/oN\Ⱥ\;m !<-Wn,c5s 4.owha5-obm1Cvۥ9N\Mga.SmnUErpfA)K7XHB̪ѿuݰR<=[8 ]]&> 0{>ד˪I8._% 5Zڞ"3NjM2.#V|)(gNie.M%;*śfr+~y[HڷSۢOI]o*-!{N&[ſbbB9ݕW-˥–Jv3NtD;&ܫdjK 5vQQ~\daMon 3­޺!:<Ť֯Xڧ3[fg6\AREc\?.>1+Jt2@+IsܽqGa.%;[9[,UV75Zеg~Ikdѩf(Q4v9a* 5kv9Ft'WiKOcv 03r7>* hlj,Y ڴq3Fe\#W[:yZ鴁$Lh֋nI3hwEn-DiŽ!j@Zxin4EMAn\1ڼF$u'2U@has"2G F** Ӆ+nȓ@gޫv[|.6lT}MiM[yTcݲڪ;sK~kʃ#N^bPVt9xaK"e=#$L(X @A{_}YќSǹoqcun2夞; VnZԔ WöԶۗ 5݁!>U}){<gq؏Jr[e'Nxi\nj.[kmClοl^7t. fwW9smQn4QJkdž 4yŴ.ffqvݳ`eV\pl 384͕@(iK|nڸP#=8ʒ{"geLA iՈ ˤop4Adwhc]2\U=,ˁbTL5[#SæmKNH&"m[zoKwM6SvݥϋZ$Lt[ߍ,[eyͭ~$ǵ[l#:=?V,R^hoYY}o-Lf#K.;o'$pRAVm]b.1޸neͨ$/m{OW.LvPX׶uKP}u-̿҉ kGpS޸m0[kwSīaHO,#^r #)BK`T&B@iZFx,#j5ݢ2p"#i_4Д1N(2*Ն l8ӸҠb)lKFB+RcjgzKgq-we|6sV'Ow.]ëW(D1Ʊm.E*vS3Z9[&ŌIpu^2q_n/\2OrFTR'.<ۨ$?sа͛wXt-D/5rX@Uͤ ɜ"(Y&p-8Qxjܶ0WjR" @zk]}EIҊG/mfjkiv(W[•b]'Lo5ikfmv\|KH_ eJIt.hN%x޹ `}+i)9{6ql-.A`q&㌲ؤae fVNwz_6EyLU~ 1٘cNJw5Gz^UX0ݨ814ZatpH0˗\S[R}1.m\}cP"~ r{·!QULxBǙV6Zi!Xd;:jՍDJ7/qn_.ջ<ҒGQ[R~lmpݐ/yd7c˰zk7E"h[3<[AA\%/9;ʬwyKm8԰İ:^~ǂ=Kq>b7(28$1ӫNmF^bs=6mfV0]mChԑ̺qhvyr,ƐA_*'M $~r'K i?x|Kyl)vktOhݴf`I[vj/fї1 `mp3h]-h$ vwW*囗mvxзl3JMé;ES~悓 KXfoHr{O/Rs W |$9mN4m)^u$Q15͵uu~eT2Q; mQU\;" f[Iߺj$:ݯ%=ۄbXj3,wSڥ^Z6@JB_o/z Ye;FPnҋ#Jz| vnmmK]6-_OnGa];>hvn`y|>T_p-/AER`\67TZ[5wͿF/wUI˧z}nå]b ]5roݛ ҟwk妶8vCަnϱH #Nl;m#Ve Z tu"2:ۡ 5LU[ne.:-fӲ&ݽDr֢q?~70ǎMUTR䁧PĸB5ƌ/ -$\GJy[dlU:*[]h#syxޭ^H{U 㫌YQ#f JAbd-7srAjde[tlj{j/6ٷkF7IFcORŻ=Fl9dW8Oa SVtmKm;Ͷce8s $mիAb57^wC7 &[lFqX3.Xb{usԶT+IéRp跢 $] ~f%g8KE]LDr.kEa(Cޢ& ')f-۹smo9nJӋrIV.++[ا -/.Ui8iogJřN9}ZڳMb5wد.1~̀`ڣh!ܻ;jKKkO:O0SG ?)o3\6|߻Zɱ޼6\[ŷm\ _iWrb o[f[V2_xuiOm>Xy\Z^6VSz5zhungb u;\Z3}asݥJ!f~\55ێu2{+Qū6 vn6pڽnFi G |uH~a$|^Al&jpxKuh apч ٫ykEJb>#Ums-uGr%5Y@.]idMH&nڳp*u-_;h۱u 4yme J(3mᶞev t/s nÏ*.դ~5js\yg46!p;_^[f -rM^ݲ&Q|u@1nک,J]`<+ة0g{fGwW~#:wZ,j6~'Bvw[@qmaܯGiOrR2w|cM7;}4_m-7K-n๯(oqnP,UhrH`?g]5P7jtD1 9n%[m&AppU+NcmDx2hܽmFYvWE l=VKj*HZn ;- K iKn%12&i\VwP Zks7Igq|]Z. ̹Ps\$yvrv-[xZ]Ҝ4l7,nsx4lYY$oZ_n'|8 6j]e)` ܤ R,y%)wu=/5̏;T8L`51ūW@] y?oܢl3DkJcHծO*E[lOf<4wh-`d# v~(6Zg6:wW9ݻݠ- jqzCa'k~ە T2*[!r]ˆ@݊leM3_-tW/]f\4?fPJ[q) ;qaf6D 鬄߶֔Skc^n7|6~΃]KE`_n8jcd '[?z&ۋB 5{ԗx%DZnUۭH ^_ 51 fɂ{~][\yط2ATkfӿWg.Rgp6D^o& rd<)G@lo3MNMj%t^h}#e'-⫂j٫V;`jcӐK] =41w}D-:WtJD\Aiعp\zFK˒ZR28V*ZmiA۽ujqہiYb䴓0z@ppqibfK΍Hv4f}ŋ|onUˋk͹*bD)WylOo),^ݸ \'{^eVF%@Q{gg\s5KQOޫl q'bjإ<+<tbĻjke\,A[\۲iJr_*)nZfbΣMr,jЗKS.<_·V9 g^_)d0>5g^ ɬF8MԺ+ms'M1Y<natK5e\w]*XRwyu ^~ƞ <˩(BwWWԼC]bQ+ж"o^0wgӛȧL\w[F~ / J/M~Ųm#( /8:grXX崉i?j[\H>Uz=6P4@[xέ٦تwΠqGNqHlvu*ˠy ;a?sPrᵅ9jsхo>5O輅,Y~JnJ[+r.1$jdYX0;WmNˍ1,P"+kti⥴U媁ť52mp`dƹ âܷ.ׯ\YXW|BJaߙKd8a)hMkr nNfKc;p=V@Wl¾$YzujVsĿet}ݯ#rl@<0Kueyhm-h^^f7.BU޽,ڶ;+֞+˯Evt? {ª ۧ6WU]=Tfwb ]nNl52;o `;+P.{ tvnܒz1fwf@9K߸\r*JKc}Jơ;TO!ie'r[ x<&ʉ+!!mи{[H QV]FQKSڮ9˕eXMB_nͫy5n&\!H0ot-0nh9K=&cM)f͋SN.߉̳6}՛d~isJOoWo#N'7ιyF$͵2[na [UASB-N=t-n\|mے%tZuWo.e`jۥ-$x:8j~ j sV0kf^[<)73p;R51yʪ4S s `3E<] ^m MPH4Tek]753\K51Jպj`XaTZzB$[j1iZSt[ vUýWҶ[D>cnn|KnKqOñex~5{5\ b AWiK)%nB i Gi yh;u\+r\X!KsũcXRV2c$8c⥅&2ýR<7&NpZ.[Դo]o/uXc8 zu|?ަS;GXN3V8,х\|K`d7uVΛqN;Uc#1hݻ̠:PE|*{u߷-pHH' VYml&  % pnAzPf0X>Vp4p>Ut҈cWG[i_w~ǚZ'/YJcQ恖ܸv _RL.14"滺rvۈ4[Z-[~`j֎+>.ZڏmV]{k2ՆKKt±湥"Ebv6<`4ponh\Y[. [ջ޹-i,ѯMq[-D]%u[vdF,-o U0ܸJώ6zQq8Wf:TO㦴/5mUٮ M 5IFsLo+ٵ(r쪲ŖI$e"vh%ubF^7? vTyw*1~[1I73Y}iRxY{,޻v'P{{ sKj[;BHoW/_2knYk*6I^~ڲ.,Bu:ݝ>Z*x(qH n{rP:d6ThPYѹ_ްu ºNY?sw)m$k'MQ lO.}[ȂאbۜMjA}GgOh*d%ضj7$%gݫ$ݸڴ,'w^y,ܨy{hC'h^V7/]0lXqvs-e{|)UvZksO* Qm8-˦nl$OϻimTlktKT,7mI{Z@kpKE_N{unA쯗k-͉+Sn]`!S=SB7t=frEmvZFhCBb[^١fAР.['I-י3=TSroiIk\o NI_jK|ـDAš~Qtt\~]{ V.lkW=[/B?ۦ65v4$iunKsPU˵sw(@z ܴ[\fvmӋaZK_۪KV݋U0ZuqU5\9-ͫV>_*,ѴN+)P:%Q J.7amX:|7oR]ZE(UWk,YA;Z]r1KhuS=苸(9aE ˔{bYX-8r?Im3xrΒBqoMsw:-2,tjI3:={ rCJ NKƫv vkd]M-[ I?}G,K' vv}ߋ\_C˭Βu OjѬ0: Q] T{jҞ*rm F-ɶ57rd.@̞?ԫ7H姸sKldJnm=6nQN]BٰUF\::2Vܽ1;ytpW-mKAIi-pYOmܺ!EO>b0H|5gU!ki n*-׮u\;_gWU1pcSqR%ˢ 0Ҷ{꽊bhe>Y;W.nW*y{&bͷlTRn߹`jKu䪒V̰m-գ˖-i!iåULtV$–5{w8(.Dﱦ[$ [Z{EYo%R1 _řHtv~Am u"iNjiV,-,'nw;e GӪS&~j,9'Zo^o.U\q`uoJvg`B""m*Ϳ-Kj+Fcۿ暽n+^. [VζE%=w,Ȥ$[ n> F.^yr͛\ǿs`ڮZM^f+E h2-[襐לm#VλjUum/kR5]Y>_׷Bt.-+{g\Z,5j7rIeDzշ$A!r)m.صƄ[i߹I 殰mXrm6{ſ!|r6,~yk ^L1ưa)9 6.Aa^nf.`{;"2 5iZLye"wڼ-bnn{ Hd+ih[at%XqR [ܦ9wb9d@Bn}7o_yOsn Tkd@8x^73̏2BXU1ec!/fR`:Tc(?+ӧMuR!eIzPӼ5iңWrO4_Hfm(Lԗn$cr"8ZdZL03~v:6cx!B[[GԵfޮ;L=LP#TmNhYU'/yZեW91ַ-4i]c{NR; r^M˘jo-{ UKP wVVۂ#׹mi6IHUxGk߫)̶XZ2~$bvnJnQg{}9.Ucvͭb Uoh7hxm/WM:NIt[cJ*N=^꺉mQvJ~%_\cQ䬑uQbU]Њ]@jͫ%KDkq̷6.NZ AЋlZOeo6m^Pջ_eKwא`. wn*ױHn[oE1~:'k9kp0szA7Xp nbqO[-.0B JDFap-hPuZZwm(;F`ort\6Wt. >crܛޯ5:wubOvvݨ߽mG!LTX'HZkSꑦ UZBtioL9K@" C?iy\DhatٯYmimLc{W)Ev3!ejP S%Ǜ55ѿn]g/¼UsY-T;GhywީkJv$]cYxI4{_̫Ο*bm+Mܹd/:bwu}Ž}@[hOZn-EZTb.s/>Rw>Ξ;U 4祾 hPNWQnwF4=fnZK2Gmٵl]B؄ J^1ŵ6kS}:k̶WC:} pIi8.ӠT;VyrIk]&p#R[.'J,K´Hc+N%E+\J%؟R+*%kW-ۨ!}JUwfR?  敮i,ݾ-q"u?v75-B.Ooyt[g\󼫎S{m{hr>"[sG~y|>E[crj-_/-CJۖ>c/\.3ذ$_[Oʇܑv)ae޵c>Nն< 'O?Xmg&s\ff'w㫷p@ i5ܡ[ǒ+ {>J㢽G#` 6o [&^jn71snZ5m/sQFSW}\88|Rٱ#L)̓M~ۡx\$Pm'뗴_48^rXSO)Q=5cMѪ绮xT!d+0]~Y.$Ů;OܧgQt>꺭3v@-EJy9uOw*Z Ufj2U_V0sq6ʭssZ-+y~BNeDnqDqk_Z͸ ڍSޮ蠊B'{uק]pjnn lbZ?Ί [-k {y2]?WV.i*O1 Vd,scd5'+{~`~PԍMrQ@m>]-00c_K"Õ\Vޮ2ř ;, b̠nt&KvwkWni[zՋĞ5#)d-)h{:=yeD2}&U~& (>YZPUgB^ub2[=7;h*a偽ط<˗´swIF0us74X5Iv: ; d'p=ΥWiAxc{~$RpPmٚwTp/R*(F2@զUFyPvy{^;6|F,Zn$Y.1kX4r0X(/_e^(#Tx[Z^›ݖ-n/hHA?\oqҒQ85n<os.洔=hySIkM4qT:]~_\`"xY:8ӯrjl}m?f|W'F\%9Di%&+זҶ+wOw )XqC=\c6O1R&߸N%/ƹEݔF)m0KæU.Vt(Mf 0oT)QIz`7Io,[X %WK1:gkW71&e+y.3 1oٮoRVh4ͣnί:3 :h^ʻ}:[Zk +mƧ37SNE4,9;uNK9Eo*'%/ۦ48RC,<`;ژ\fQ E"|u(]>ضjZj빲 [IE~i;:d$ٶlai./%!-&u_?˦X2B2qgnޯ%)gWRK!dZJں_fv7Okʿ˓ [*ޏl^M3q `j"݇͞mz~cgSg:'[y@̒~+E?4[cw_ZVQZhFZmjV/r%xQ 8jڛMHpuc>V՛d N7@D` )4|.:͇Wmtm@ҫܯskkط_3*3[])GWa̾qs-Uh/ZPl8ӥFk|'MokJݶJ~{v^dq*CBhYUCwvմdIq^nwۺ {\a-+D 6|ZwBU;]$\VWοۻⅷpr ?v^8exT*6snnڝ xMs\ڃskWy;\TXyeFټQ.] wmGwh+,$ Ebf\t[HY# yݡ]cL޼@DVֿZp/tǹfy,Y_4f7ʚ|[RC_n/)6ܟ1nk4ٶVZy5k.0T-'ܒ\|ni@Վ\|Fvn{_;APQ4y5fdݮk:청OB륻jט~)#Q=&ݯWQ6)6_cW,h&ո'i$^hҚeq7\a'7kQOΛA-Dרn&ӸEr3'B˝΃.t)l֖ ZG/DXKzφ LgV*@@Ԝ:W9nk:-ϘBqjt!WuEs6:|u|y- ;wE vKweoo8bgr)͕D?R]kL0QJ\ޭVӦսS2β7ct9 @f~,*@{7!ւѺV $וnOX|ah vջ{#S2JVCsTo]K[RC\ʓN9{Djavcc=]o*JY؃]W<(1*x-WROOp2[ir*1*R˯E79krg-cUozԆN]*Gr4nkQ{w;\2ڮ&v^[չo_Pu@?,ˆqFw/7r}ݱտ\k]Q '{W]EBҿ~lKK%#iO۹{Zp(ցg^˷ 㚱czzo7ݳPr[:.  #0[Lo\gu+anz.I;V=g+ː<Uas<_s*bߙksAnvNeooGnѻUM@dP(nkjaU(AiOV!vׂe=P Zbf*֖@5(@Fw)Q'Nh>]Mbm^PuR1*F\X Ԋ_@ H@ż聂-=Rypj3 K ᤸ`G5Pu_P.Xӥ*ƅB P&U/mZ㙵d@R{.?Wˢdﺏ/rܽ;3.؍>ΪuNcM˨Ł^\nbuQ z]wܻyˀp ?tUۈzrUkѾ]YrV P~%~5{]mڶkնԖ$qUNUn9,HvRX&yf. sq5p֭[8>}Dw w”ޔK)ܱm{mR-!'[ڥBcoKodQv (סnv5?Hf͇oMz UNJ-7ձqH!|'зmK\$lL< Zllm№Hm*.HRywnyypy:\[똷ʿI{G|6R2g'z*}*,]`/i͢59#EdP˵goIf ;6L -v(Gz֨tr쮟i_[AkWnvZ [Ҷ>]^ zիVҁ hs~jmԴKm,{ȸQ:)\ DHS( An<̙=;UIfmDErI uYA]a{E9,yyB7RY5a{u\p?f*>#2÷\ʮmyB:II=$;c"[lA--i\mطB/rbcg8sc WžO\{Q媻mhi*.+^y8n{T-!qt {vZUʹcLI7bxr[qJr"X(V+(5lmr}5Q:w}0>agB_~B0X&g*Vb m3s^bF@ ,wk[ Bx7Pn5jQO0,-İޓ] bv_ -@`",?5avWrL-[QehfC7d ⫖*+N;5_DIuU̐iP'O2³t]kVdem֐t4#f[00Y@|'"(sH[vTV18k><5+;uS:]1iJ#T Nr.sOS!Fr æIM~aw&`?+إ(dYP ؍>R ީGnvҀ-P1u9Mֻѻ~bW"S]f ˇ_د>Ն d0|gKE=כ=y} j4m&j KEV> 5WPGs0 7.] ,vO'/sm#3v5ծ]%NQz@gbZ'k,c{:t5%.iB̹r2~#!A 2^Pm+`F$wiFپ|4,6CD-]NYu^'˖͜.AHBή1~= PaElV;GAc#%BDBjFvmp.IbqyqJu'A#Mj $Ȣ`;-Zu$!vጃW)m+,D(^:⠱2b4D25%mps:.\,o1SVdA*X+N.m#گmݡvfjЛF'+k%`8Ka$B٪'C1} lƶ$wQwi79eviB}Qy]ULn|h-X-L62EJV~/Lw04;N*b ĕ*>ISо׎LI8b{kW.p* q/z=y+12f3\* 1%m*ݠ" A\dqM;ƻnb'uA$ HtdWʅfFx4Q#ӎ5ZG6/E amj՛ #[Z('1-UY!ÃU?\ b1+KuymNI;Q|Thתݰ1 ޕ}Mxğ C`:{MZJ7^RL4/W/ q8S5daK;y]GiXfۣ[!SHQ|7CG[p7nҁHUj7&ӍZ֘Vm -KqHGEZ@F7^n&*1Z~%1QIpoܘMwkwI-"WV*Y\Wc5i7vק+z-XR$i+6cn=/ro$*yl*e*~e!Լ6ʽ_ʕ\.wn naFݦHѺ[U j'2;Tы\2_, wMi.)b3@v]Ko*iVr*mpqo5*(%Bhno즞:m0*v?zqXTPcqA;wB!F0pзb3jRt V2̺Eik3ʺvt[ViEn(S ijn iᵫy QmwBckzK@O3x*m>߲eYŰ 25W;|^//W\ZQ 1wFjsJr f:Lm dKENC\ton\1*;LVՆĪ;.N`ɲeU (I㎦wJ8hyZ): Ն)ԫew1U rą7zź$2gi{è$Td~K ';TԶ5JoT ڠ'}F>xAݫV Y=FjEc0pBU]u9I̱$⊄Fg}y17;PhRrWMeh\PգcR0bmLn&-VS{(e W$-x&@*0kw}{>ŪQt7PglB({z 㽹F Sp0–b̸sAv8@(+t/DMW*tz^mOJfZ>O*@VTTIĚ7݅3]T `Kj[#?iz[Ű^mѬ$eO2B!I!]cN2( WVnM9&n4|17{[C$ꦚq.DIB]#}Tuz+Lv/mʾ `AÝVsۧ#)rqؠm: 3Rp@9 "8gګqwml}s@Ev,?y3 t֐iKgpvkbWT#?pWզCZt/.nfe2T\k$-m٥|fR%;%&])='v l49KgUc_.?ڍ$դץw5R]N вİXa%m$n;H_,4j UFǴ(+lF}">[K``;j[ҖEM?ʳth$U1 P-=SdfOVx,kJgO9NZޮfNNnM._ʯ*/bmv[B'qPMDǥKwb.?۹ءn؁ڎ&j>z fWgU v@KZN=VfF,;փmٖZ;_vdxSa@$ Hm4Veq [H8L#i6Z[bDinK FJM`"M2!ɉ]M4 Q?U-0䉠LGMH%`uǠZM'{˶+oc YZQ뎲jiF^bԗE*mpא\ gfDKZ{Ԛ| j8~\ C5;Ej]rS*n g* ]DQ;@ T. 'dT͇kWnz'U눒~bD ZSSAēyQ5H~j@vOiA5M/uĢAP^qܵtyn,o\:\V$|G yv%j]֯c#F2[qK6]: z OuuvX۽H m[]n ub.T$ow.S]ujhiI˄7oBܾ ǂ1ZZk"ˁ! w|˾_a~Җ,cF\IVhۗHs;{yikpJ[k+ς Ŏ3B8^e4Z{@'Qc5 ԾhES^+kS.BPίvCiP0j9j۴jڧi%;Ʀb֎1՛;cU8`fCwݭi`~58}XQS&;㫆I-v̘ϬUU +d{AȰgNc(8jvlSPa<+V ]GW1h MiE2}e]"- 0vjbn&r, p^`h ]GFPE>#_f,3}?nH@ B5\K]bVTBqΪ 2Uer +C.t&4.-LUvz ZE#`4l`I9U@wM\bL&ITB PNf(jN KƖݵ8jtujZԀϺqQ0Zrw~ŕ7(YC`N;4KNGBp7y>}Jh(q٤T!sZ|VRK$ bxWkVl"dB;$[ ibp֮qmCc!ޫ\ɷ2:V#:c$tMH a'6B]$ZeĹ8UցT/P.v-zeU?Z`KMj1)DsPC1ғ}> b݃t n-!o]К|KEzӭ2#KC+`x-PN NWҊq 3saܻrd 6Afjaox+Pq?yrT;19RMUn Ǔw﷦Zn9똾tYijvj0b|&7-Woٹkg_3Z?`m^Xlܜ@I#7Qlt[÷[Aiy3=W;m=%uܥ2I"4xBFYY0O ^8FʙRN7OvC\9;l܂@~$thagf6׼mXBAdĺ r{mU-URRrh\ Ʉ7q*:e@ݭxLWfTh1Ip6;Nǻ[d՛J8{vwaיʩ7/I;EäaĴm8jBծi iFmtstL1 g5ar+P+ f &Ro Vڽu>w>f7rdRty' ݤg/t+jGR[MܸFRRKSٯJ5je7K7béoA 8zlcQ^o0[ 4olFj[wv\ڧW?3wvp}ܧk ܵHealg_!N,v7[-|nd.lW,׮!3Ҩ~*ʗ{Eu6tE=ψ m%T1FךlY_Vi+OJ{@ ejon|:/󄁭\%ڳgN\,boҽӬU\ۃp궜 ^g3,I;Kdu'j:O@ͫzB+DjI:tUiQaJ˥`Kp%J7MSUTƬ>;*wUA@ jWawu1;@*T(4E\quչPwjj Le>2ƅSBwt4>[ ;M\W}>G]yp+Ff'3٫Wmtj i p2$M$@$ijaA E.@WmY M =N' :.0'UiCrd椳aucbi`ט:+X e hye7#L"^PE7 ڐRzi]=_͢;'Ow%aET/\aBUe /Ԥjy:;(k8M'r`3ꩻ;!{@ٷrp&IE71g+CAO-0 V5';0DPl7=5Hۦ$.Vl ̀wm.\rI}GBrQ{uy*o739 9h1} iI3L)mҤ-^Ȣ9k#jvéͥbm,\O(i8lPcH#k,Z]69u;g\KoȖs|cǧv-)>w*4:-K|Cz <\ÿҥ󹲰/u~~\7Y. Z÷r^e!>ѧb+?R\t*boǫEor3b5A7ZW]3Q*w7iUT[k{nQ Y\K/б`6QݪnU>ƽBBs}ՄCx~ڴ! ,ukVhM*xt-Tr{w;M@s gxc'QjVC'!qin-ì?q/[8Mȓ)_Sra2 |K{9OP*yXи i}@;Cz#eh)2t{5unmI$)G*5yvP`0Ib '49j9aKf5F⬳!A*MuH#ZY`6.pe<*lF wMYRx=Tos08j,:#;ԫlyh-4.:oiʶBZe]yM֋ =T7.޿i9ʰO.zV\/NZ ;n 4Ӵ=3=4vU-kz:IEjV=j[6-F2YχE-sA;b(z0$Z+0KJqo3_#bT ih mxMTntiXϘv]-%s ?߱W\Wu59CM#_h+ʧG77vQ8T[qjlKhߋy+iJ Iۼv)B-*ZRۖT>*cB򭲮'1*]嬆{4U#`;Hf< טTĴK.b{ޫU)93Wh'\'PWQgok94AqFp+C M0,_ڤӚ9ӠF"_FfkW6+VeFkNW#'P3LVm.1}0ݠ`4muv4XkI&1[=JУFkA89i9t;7Eyup(Q;JͳS0089p&6V39,Sja$;T֧4MRBPc{lI̟ Cxa0f*:\ piԝv$*QGO[$5ULjPv/S #b;# (Q׹{tt"$V("z:! b1rtwLdp'g@a'GHLcd\=gg8g 'xT.!fj-:T1W9۲\@`14}s[$klS[o̴؍p֐FnaRZĖ{M٤-db<3H1ԭ-ڹv5 nxlrev#wμIt^6$Ê0ԝ?h c7Hw|چV!rvxhmsH*̀vim7"6צg͠5zYڋn(IҢʕ NqG[ @hEo2㰐/׫yhXNϼիvW /wPaq8")*OӭO,L'v^eāZQ^Ϳm g1i0#/][Ɂ[ci~!<CNTܵ8F _vp"aLQ:m8tWecx^-R'=%Kd'Of@zb WQ,Zwg/2fŭ)6X%vwQuQKEW" 9{Ad[qCvg}M 3Ff\+RlF"\;14(=4܁B< ^aNPڎ\!{V^i4I(ҕ$;(hY!r^;< LN=^^ d%.uP$n~]d.%^BB۵Tq=b k Da+q4,1`fvm--ǀYc|5tJ=wRͨ>jAiGmp7Q s^,b'z:VAxq=5!'i#r*c"gh fLm ~"别6hm \hqUvegiL$" l40# rީxۤeFb24np É]\S%n]qcZW`KS4nES9z=B+H 'Oz#=t[a>"IE(Z~ZBEGbͭH,fhCX1U&ޫ0y?,bյ!W ub>ۜ;JeJm٧1>ջv3ì?A cY6@5yAa1(=5BX"anXA.@*p۸،6Patd3,!KFfܹCf2jd@YFMk#tSGSq ;F7 @&S00 +VykyY\}55 :@5pөmS j@UP!NX}zmӧnlo]t^ۥEfGc\!w;QfV˙kF¸{ǵlb>^0nN6;Kٖv޹VYL_[8`4QN:Kܽgǹ}ݺW-7t|ĖcLҁg>б@HUHcxo#;wM; *vrFKFޫVghn +PW.@~޶s$iCjYYjv`kwv ֐E*ݍWxz( ĈKFf^\-rLeN.O5(QϫBک,*"qTW/+[cx(¯70ܧòAMCfxOU]VTw 9wb?IXe'[wݭ] ƸB;z& lc^iTa[c ڠxy_  ^&25= OShE\iPuTY [k!Wnzk[Vrq>K͙<=Q vdIQɟiQ&"ڣyqqTiueD9{OZ#X:μhЖAY't( 0Q'Lss8{V&g>=%N#L҂ڧ> wkS?r\$C qhx<ăFm _^҃@:@(4yr´]Fz OjLy.vS '5.tjc3ޭ77ۖᲵlduT"FAB-|TV [gb%7ʁ@ 8qhuM!9~/j|~ x>(v*<)^apGE:a3ᶚ(0MbaOz9<^_gU3&BEl[an= j\c٩ykfmW ܠ 9B˹wʆ7GoP`Mh7dt)WX 2|?ZtOE,7-85}{?WrˊQ=]u[ fsFnltw|e0[c* mF&{v$F my7p0;jl &o9(fUxMੴL 2L ;cFi'F8U+bXxt .6.},n|$ŧGH=Tޫlbvq4 =B.&*T-_#S2:lu[Kq1g`Sx~hs( 4LwSVݰJY40!SŦ JY(߫J։ru9[q$OV\5 E w?>*'+oQtPݹݵkqFK9oQr_SZ\5T"ՀJ[4q婫IRn2vk?0w(dۆ+]6,pөsx˒H#uOsh4NRi;Tv*_LũxU;y,I8}\i_NL-ܛ @N/]OOMZtZ:MqO*Lcz /qm{tmX{U:L;mMs0=*n?-W/nVQؠ,9Ҭv۝,Wſi7Px"?xTiTC1ۇf"m- |:EQxj-)j]>rBIW Q" fcLгkڮWaA5w \dOein2ˑ =4Ń.{;49-!"_xUUQnpm, boom require 'rubygems' rescue nil require 'bacon' end # Set the MEMCACHED environment variable as follows to enable testing # of the MemCached meta and entity stores. ENV['MEMCACHED'] ||= 'localhost:11211' $memcached = nil $dalli = nil def have_memcached?(server=ENV['MEMCACHED']) return $memcached unless $memcached.nil? # silence warnings from memcached begin v, $VERBOSE = $VERBOSE, nil require 'memcached' ensure $VERBOSE = v end $memcached = Memcached.new(server) $memcached.set('ping', '') true rescue LoadError => boom warn "memcached library not available. related tests will be skipped." $memcached = false false rescue => boom warn "memcached not working. related tests will be skipped." $memcached = false false end have_memcached? def have_dalli?(server=ENV['MEMCACHED']) return $dalli unless $dalli.nil? require 'dalli' $dalli = Dalli::Client.new(server) $dalli.set('ping', '') true rescue LoadError => boom warn "dalli library not available. related tests will be skipped." $dalli = false false rescue => boom warn "dalli not working. related tests will be skipped." $dalli = false false end have_dalli? def need_dalli(forwhat) yield if have_dalli? end def need_memcached(forwhat) yield if have_memcached? end def need_java(forwhat) yield if RUBY_PLATFORM =~ /java/ end # Setup the load path .. $LOAD_PATH.unshift File.dirname(File.dirname(__FILE__)) + '/lib' $LOAD_PATH.unshift File.dirname(__FILE__) require 'rack/cache' # Methods for constructing downstream applications / response # generators. module CacheContextHelpers # The Rack::Cache::Context instance used for the most recent # request. attr_reader :cache # An Array of Rack::Cache::Context instances used for each request, in # request order. attr_reader :caches # The Rack::Response instance result of the most recent request. attr_reader :response # An Array of Rack::Response instances for each request, in request order. attr_reader :responses # The backend application object. attr_reader :app def setup_cache_context # holds each Rack::Cache::Context @app = nil # each time a request is made, a clone of @cache_template is used # and appended to @caches. @cache_template = nil @cache = nil @caches = [] @errors = StringIO.new @cache_config = nil @called = false @request = nil @response = nil @responses = [] @storage = Rack::Cache::Storage.new end def teardown_cache_context @app, @cache_template, @cache, @caches, @called, @request, @response, @responses, @cache_config, @cache_prototype = nil end # A basic response with 200 status code and a tiny body. def respond_with(status=200, headers={}, body=['Hello World'], &bk) called = false @app = lambda do |env| called = true response = Rack::Response.new(body, status, headers) request = Rack::Request.new(env) bk.call(request, response) if bk response.finish end @app.meta_def(:called?) { called } @app.meta_def(:reset!) { called = false } @app end def cache_config(&block) @cache_config = block end def request(method, uri='/', opts={}) opts = { 'rack.run_once' => true, 'rack.errors' => @errors, 'rack-cache.storage' => @storage }.merge(opts) fail 'response not specified (use respond_with)' if @app.nil? @app.reset! if @app.respond_to?(:reset!) @cache_prototype ||= Rack::Cache::Context.new(@app, &@cache_config) @cache = @cache_prototype.clone @caches << @cache @request = Rack::MockRequest.new(@cache) yield @cache if block_given? @response = @request.request(method.to_s.upcase, uri, opts) @responses << @response @response end def get(stem, env={}, &b) request(:get, stem, env, &b) end def head(stem, env={}, &b) request(:head, stem, env, &b) end def post(*args, &b) request(:post, *args, &b) end end module TestHelpers include FileUtils F = File @@temp_dir_count = 0 def create_temp_directory @@temp_dir_count += 1 path = F.join(Dir.tmpdir, "rack-cache-#{$$}-#{@@temp_dir_count}") mkdir_p path if block_given? yield path remove_entry_secure path end path end def create_temp_file(root, file, data='') path = F.join(root, file) mkdir_p F.dirname(path) F.open(path, 'w') { |io| io.write(data) } end end class Bacon::Context include TestHelpers include CacheContextHelpers end # Metaid == a few simple metaclass helper # (See http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html.) class Object # The hidden singleton lurks behind everyone def metaclass; class << self; self; end; end def meta_eval(&blk); metaclass.instance_eval(&blk); end # Adds methods to a metaclass def meta_def name, &blk meta_eval { define_method name, &blk } end # Defines an instance method within a class def class_def name, &blk class_eval { define_method name, &blk } end # True when the Object is neither false or nil. def truthy? !!self end end rack-cache-1.2/test/entitystore_test.rb0000644000000000000000000001725111745752772017016 0ustar rootroot# coding: utf-8 require "#{File.dirname(__FILE__)}/spec_setup" require 'rack/cache/entitystore' class Object def sha_like? length == 40 && self =~ /^[0-9a-z]+$/ end end shared 'A Rack::Cache::EntityStore Implementation' do it 'responds to all required messages' do %w[read open write exist?].each do |message| @store.should.respond_to message end end it 'stores bodies with #write' do key, size = @store.write(['My wild love went riding,']) key.should.not.be.nil key.should.be.sha_like data = @store.read(key) data.should.equal 'My wild love went riding,' end it 'takes a ttl parameter for #write' do key, size = @store.write(['My wild love went riding,'], 0) key.should.not.be.nil key.should.be.sha_like data = @store.read(key) data.should.equal 'My wild love went riding,' end it 'correctly determines whether cached body exists for key with #exist?' do key, size = @store.write(['She rode to the devil,']) @store.should.exist key @store.should.not.exist '938jasddj83jasdh4438021ksdfjsdfjsdsf' end it 'can read data written with #write' do key, size = @store.write(['And asked him to pay.']) data = @store.read(key) data.should.equal 'And asked him to pay.' end it 'gives a 40 character SHA1 hex digest from #write' do key, size = @store.write(['she rode to the sea;']) key.should.not.be.nil key.length.should.equal 40 key.should.be =~ /^[0-9a-z]+$/ key.should.equal '90a4c84d51a277f3dafc34693ca264531b9f51b6' end it 'returns the entire body as a String from #read' do key, size = @store.write(['She gathered together']) @store.read(key).should.equal 'She gathered together' end it 'returns nil from #read when key does not exist' do @store.read('87fe0a1ae82a518592f6b12b0183e950b4541c62').should.be.nil end it 'returns a Rack compatible body from #open' do key, size = @store.write(['Some shells for her hair.']) body = @store.open(key) body.should.respond_to :each buf = '' body.each { |part| buf << part } buf.should.equal 'Some shells for her hair.' end it 'returns nil from #open when key does not exist' do @store.open('87fe0a1ae82a518592f6b12b0183e950b4541c62').should.be.nil end it 'can store largish bodies with binary data' do pony = File.open(File.dirname(__FILE__) + '/pony.jpg', 'rb') { |f| f.read } key, size = @store.write([pony]) key.should.equal 'd0f30d8659b4d268c5c64385d9790024c2d78deb' data = @store.read(key) data.length.should.equal pony.length data.hash.should.equal pony.hash end it 'deletes stored entries with #purge' do key, size = @store.write(['My wild love went riding,']) @store.purge(key).should.be.nil @store.read(key).should.be.nil end end describe 'Rack::Cache::EntityStore' do describe 'Heap' do before { @store = Rack::Cache::EntityStore::Heap.new } behaves_like 'A Rack::Cache::EntityStore Implementation' it 'takes a Hash to ::new' do @store = Rack::Cache::EntityStore::Heap.new('foo' => ['bar']) @store.read('foo').should.equal 'bar' end it 'uses its own Hash with no args to ::new' do @store.read('foo').should.be.nil end end describe 'Disk' do before do @temp_dir = create_temp_directory @store = Rack::Cache::EntityStore::Disk.new(@temp_dir) end after do @store = nil remove_entry_secure @temp_dir end behaves_like 'A Rack::Cache::EntityStore Implementation' it 'takes a path to ::new and creates the directory' do path = @temp_dir + '/foo' @store = Rack::Cache::EntityStore::Disk.new(path) File.should.be.a.directory path end it 'produces a body that responds to #to_path' do key, size = @store.write(['Some shells for her hair.']) body = @store.open(key) body.should.respond_to :to_path path = "#{@temp_dir}/#{key[0..1]}/#{key[2..-1]}" body.to_path.should.equal path end it 'spreads data over a 36² hash radius' do (<<-PROSE).each_line { |line| @store.write([line]).first.should.be.sha_like } My wild love went riding, She rode all the day; She rode to the devil, And asked him to pay. The devil was wiser It's time to repent; He asked her to give back The money she spent My wild love went riding, She rode to sea; She gathered together Some shells for her hair She rode on to Christmas, She rode to the farm; She rode to Japan And re-entered a town My wild love is crazy She screams like a bird; She moans like a cat When she wants to be heard She rode and she rode on She rode for a while, Then stopped for an evening And laid her head down By this time the weather Had changed one degree, She asked for the people To let her go free My wild love went riding, She rode for an hour; She rode and she rested, And then she rode on My wild love went riding, PROSE subdirs = Dir["#{@temp_dir}/*"] subdirs.each do |subdir| File.basename(subdir).should.be =~ /^[0-9a-z]{2}$/ files = Dir["#{subdir}/*"] files.each do |filename| File.basename(filename).should.be =~ /^[0-9a-z]{38}$/ end files.length.should.be > 0 end subdirs.length.should.equal 28 end end need_memcached 'entity store tests' do describe 'MemCached' do before do @store = Rack::Cache::EntityStore::MemCached.new($memcached) end after do @store = nil end behaves_like 'A Rack::Cache::EntityStore Implementation' end describe 'options parsing' do before do uri = URI.parse("memcached://#{ENV['MEMCACHED']}/obj_ns1?show_backtraces=true") @memcached_metastore = Rack::Cache::MetaStore::MemCached.resolve uri end it 'passes options from uri' do @memcached_metastore.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true end it 'takes namespace into account' do @memcached_metastore.cache.instance_variable_get(:@options)[:prefix_key].should.equal 'obj_ns1' end end end need_dalli 'entity store tests' do describe 'Dalli' do before do $dalli.flush_all @store = Rack::Cache::EntityStore::Dalli.new($dalli) end after do @store = nil end behaves_like 'A Rack::Cache::EntityStore Implementation' end describe 'options parsing' do before do uri = URI.parse("memcached://#{ENV['MEMCACHED']}/obj_ns1?show_backtraces=true") @dalli_metastore = Rack::Cache::MetaStore::Dalli.resolve uri end it 'passes options from uri' do @dalli_metastore.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true end it 'takes namespace into account' do @dalli_metastore.cache.instance_variable_get(:@options)[:namespace].should.equal 'obj_ns1' end end end need_java 'entity store testing' do module Rack::Cache::AppEngine module MC class << (Service = {}) def contains(key); include?(key); end def get(key); self[key]; end; def put(key, value, ttl = nil) self[key] = value end end end end describe 'GAEStore' do before do puts Rack::Cache::AppEngine::MC::Service.inspect @store = Rack::Cache::EntityStore::GAEStore.new end after do @store = nil end behaves_like 'A Rack::Cache::EntityStore Implementation' end end end rack-cache-1.2/test/cachecontrol_test.rb0000644000000000000000000001277111745752772017073 0ustar rootrootrequire "#{File.dirname(__FILE__)}/spec_setup" require 'rack/cache/cachecontrol' describe 'Rack::Cache::CacheControl' do it 'takes no args and initializes with an empty set of values' do cache_control = Rack::Cache::CacheControl.new cache_control.should.be.empty cache_control.to_s.should.equal '' end it 'takes a String and parses it into a Hash when created' do cache_control = Rack::Cache::CacheControl.new('max-age=600, foo') cache_control['max-age'].should.equal '600' cache_control['foo'].should.be.true end it 'takes a String with a single name=value pair' do cache_control = Rack::Cache::CacheControl.new('max-age=600') cache_control['max-age'].should.equal '600' end it 'takes a String with multiple name=value pairs' do cache_control = Rack::Cache::CacheControl.new('max-age=600, max-stale=300, min-fresh=570') cache_control['max-age'].should.equal '600' cache_control['max-stale'].should.equal '300' cache_control['min-fresh'].should.equal '570' end it 'takes a String with a single flag value' do cache_control = Rack::Cache::CacheControl.new('no-cache') cache_control.should.include 'no-cache' cache_control['no-cache'].should.be.true end it 'takes a String with a bunch of all kinds of stuff' do cache_control = Rack::Cache::CacheControl.new('max-age=600,must-revalidate,min-fresh=3000,foo=bar,baz') cache_control['max-age'].should.equal '600' cache_control['must-revalidate'].should.be.true cache_control['min-fresh'].should.equal '3000' cache_control['foo'].should.equal 'bar' cache_control['baz'].should.be.true end it 'strips leading and trailing spaces from header value' do cache_control = Rack::Cache::CacheControl.new(' public, max-age = 600 ') cache_control.should.include 'public' cache_control.should.include 'max-age' cache_control['max-age'].should.equal '600' end it 'strips blank segments' do cache_control = Rack::Cache::CacheControl.new('max-age=600,,max-stale=300') cache_control['max-age'].should.equal '600' cache_control['max-stale'].should.equal '300' end it 'removes all directives with #clear' do cache_control = Rack::Cache::CacheControl.new('max-age=600, must-revalidate') cache_control.clear cache_control.should.be.empty end it 'converts self into header String with #to_s' do cache_control = Rack::Cache::CacheControl.new cache_control['public'] = true cache_control['max-age'] = '600' cache_control.to_s.split(', ').sort.should.equal ['max-age=600', 'public'] end it 'sorts alphabetically with boolean directives before value directives' do cache_control = Rack::Cache::CacheControl.new('foo=bar, z, x, y, bling=baz, zoom=zib, b, a') cache_control.to_s.should.equal 'a, b, x, y, z, bling=baz, foo=bar, zoom=zib' end it 'responds to #max_age with an integer when max-age directive present' do cache_control = Rack::Cache::CacheControl.new('public, max-age=600') cache_control.max_age.should.equal 600 end it 'responds to #max_age with nil when no max-age directive present' do cache_control = Rack::Cache::CacheControl.new('public') cache_control.max_age.should.be.nil end it 'responds to #shared_max_age with an integer when s-maxage directive present' do cache_control = Rack::Cache::CacheControl.new('public, s-maxage=600') cache_control.shared_max_age.should.equal 600 end it 'responds to #shared_max_age with nil when no s-maxage directive present' do cache_control = Rack::Cache::CacheControl.new('public') cache_control.shared_max_age.should.be.nil end it 'responds to #public? truthfully when public directive present' do cache_control = Rack::Cache::CacheControl.new('public') cache_control.should.be.public end it 'responds to #public? non-truthfully when no public directive present' do cache_control = Rack::Cache::CacheControl.new('private') cache_control.should.not.be.public end it 'responds to #private? truthfully when private directive present' do cache_control = Rack::Cache::CacheControl.new('private') cache_control.should.be.private end it 'responds to #private? non-truthfully when no private directive present' do cache_control = Rack::Cache::CacheControl.new('public') cache_control.should.not.be.private end it 'responds to #no_cache? truthfully when no-cache directive present' do cache_control = Rack::Cache::CacheControl.new('no-cache') cache_control.should.be.no_cache end it 'responds to #no_cache? non-truthfully when no no-cache directive present' do cache_control = Rack::Cache::CacheControl.new('max-age=600') cache_control.should.not.be.no_cache end it 'responds to #must_revalidate? truthfully when must-revalidate directive present' do cache_control = Rack::Cache::CacheControl.new('must-revalidate') cache_control.should.be.must_revalidate end it 'responds to #must_revalidate? non-truthfully when no must-revalidate directive present' do cache_control = Rack::Cache::CacheControl.new('max-age=600') cache_control.should.not.be.no_cache end it 'responds to #proxy_revalidate? truthfully when proxy-revalidate directive present' do cache_control = Rack::Cache::CacheControl.new('proxy-revalidate') cache_control.should.be.proxy_revalidate end it 'responds to #proxy_revalidate? non-truthfully when no proxy-revalidate directive present' do cache_control = Rack::Cache::CacheControl.new('max-age=600') cache_control.should.not.be.no_cache end end rack-cache-1.2/test/metastore_test.rb0000644000000000000000000002531011745752772016423 0ustar rootrootrequire "#{File.dirname(__FILE__)}/spec_setup" require 'rack/cache/metastore' require 'rack/cache/entitystore' shared 'A Rack::Cache::MetaStore Implementation' do ### # Helpers def mock_request(uri, opts) env = Rack::MockRequest.env_for(uri, opts || {}) Rack::Cache::Request.new(env) end def mock_response(status, headers, body) headers ||= {} body = Array(body).compact Rack::Cache::Response.new(status, headers, body) end def slurp(body) buf = '' body.each { |part| buf << part } buf end # Stores an entry for the given request args, returns a url encoded cache key # for the request. def store_simple_entry(*request_args) path, headers = request_args @request = mock_request(path || '/test', headers || {}) @response = mock_response(200, {'Cache-Control' => 'max-age=420'}, ['test']) body = @response.body cache_key = @store.store(@request, @response, @entity_store) @response.body.should.not.be.same_as body cache_key end before do @request = mock_request('/', {}) @response = mock_response(200, {}, ['hello world']) end after do @store = nil @entity_store = nil end # Low-level implementation methods =========================================== it 'writes a list of negotation tuples with #write' do lambda { @store.write('/test', [[{}, {}]]) }.should.not.raise end it 'reads a list of negotation tuples with #read' do @store.write('/test', [[{},{}],[{},{}]]) tuples = @store.read('/test') tuples.should.equal [ [{},{}], [{},{}] ] end it 'reads an empty list with #read when nothing cached at key' do @store.read('/nothing').should.be.empty end it 'removes entries for key with #purge' do @store.write('/test', [[{},{}]]) @store.read('/test').should.not.be.empty @store.purge('/test') @store.read('/test').should.be.empty end it 'succeeds when purging non-existing entries' do @store.read('/test').should.be.empty @store.purge('/test') end it 'returns nil from #purge' do @store.write('/test', [[{},{}]]) @store.purge('/test').should.be.nil @store.read('/test').should.equal [] end %w[/test http://example.com:8080/ /test?x=y /test?x=y&p=q].each do |key| it "can read and write key: '#{key}'" do lambda { @store.write(key, [[{},{}]]) }.should.not.raise @store.read(key).should.equal [[{},{}]] end end it "can read and write fairly large keys" do key = "b" * 4096 lambda { @store.write(key, [[{},{}]]) }.should.not.raise @store.read(key).should.equal [[{},{}]] end it "allows custom cache keys from block" do request = mock_request('/test', {}) request.env['rack-cache.cache_key'] = lambda { |request| request.path_info.reverse } @store.cache_key(request).should == 'tset/' end it "allows custom cache keys from class" do request = mock_request('/test', {}) request.env['rack-cache.cache_key'] = Class.new do def self.call(request); request.path_info.reverse end end @store.cache_key(request).should == 'tset/' end it 'does not blow up when given a non-marhsalable object with an ALL_CAPS key' do store_simple_entry('/bad', { 'SOME_THING' => Proc.new {} }) end # Abstract methods =========================================================== it 'stores a cache entry' do cache_key = store_simple_entry @store.read(cache_key).should.not.be.empty end it 'sets the X-Content-Digest response header before storing' do cache_key = store_simple_entry req, res = @store.read(cache_key).first res['X-Content-Digest'].should.equal 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3' end it 'finds a stored entry with #lookup' do store_simple_entry response = @store.lookup(@request, @entity_store) response.should.not.be.nil response.should.be.kind_of Rack::Cache::Response end it 'does not find an entry with #lookup when none exists' do req = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'}) @store.lookup(req, @entity_store).should.be.nil end it "canonizes urls for cache keys" do store_simple_entry(path='/test?x=y&p=q') hits_req = mock_request(path, {}) miss_req = mock_request('/test?p=x', {}) @store.lookup(hits_req, @entity_store).should.not.be.nil @store.lookup(miss_req, @entity_store).should.be.nil end it 'does not find an entry with #lookup when the body does not exist' do store_simple_entry @response.headers['X-Content-Digest'].should.not.be.nil @entity_store.purge(@response.headers['X-Content-Digest']) @store.lookup(@request, @entity_store).should.be.nil end it 'restores response headers properly with #lookup' do store_simple_entry response = @store.lookup(@request, @entity_store) response.headers. should.equal @response.headers.merge('Content-Length' => '4') end it 'restores response body from entity store with #lookup' do store_simple_entry response = @store.lookup(@request, @entity_store) body = '' ; response.body.each {|p| body << p} body.should.equal 'test' end it 'invalidates meta and entity store entries with #invalidate' do store_simple_entry @store.invalidate(@request, @entity_store) response = @store.lookup(@request, @entity_store) response.should.be.kind_of Rack::Cache::Response response.should.not.be.fresh end it 'succeeds quietly when #invalidate called with no matching entries' do req = mock_request('/test', {}) @store.invalidate(req, @entity_store) @store.lookup(@request, @entity_store).should.be.nil end # Vary ======================================================================= it 'does not return entries that Vary with #lookup' do req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'}) req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'}) res = mock_response(200, {'Vary' => 'Foo Bar'}, ['test']) @store.store(req1, res, @entity_store) @store.lookup(req2, @entity_store).should.be.nil end it 'stores multiple responses for each Vary combination' do req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'}) res1 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 1']) key = @store.store(req1, res1, @entity_store) req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'}) res2 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 2']) @store.store(req2, res2, @entity_store) req3 = mock_request('/test', {'HTTP_FOO' => 'Baz', 'HTTP_BAR' => 'Boom'}) res3 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 3']) @store.store(req3, res3, @entity_store) slurp(@store.lookup(req3, @entity_store).body).should.equal 'test 3' slurp(@store.lookup(req1, @entity_store).body).should.equal 'test 1' slurp(@store.lookup(req2, @entity_store).body).should.equal 'test 2' @store.read(key).length.should.equal 3 end it 'overwrites non-varying responses with #store' do req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'}) res1 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 1']) key = @store.store(req1, res1, @entity_store) slurp(@store.lookup(req1, @entity_store).body).should.equal 'test 1' req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'}) res2 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 2']) @store.store(req2, res2, @entity_store) slurp(@store.lookup(req2, @entity_store).body).should.equal 'test 2' req3 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'}) res3 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 3']) @store.store(req3, res3, @entity_store) slurp(@store.lookup(req1, @entity_store).body).should.equal 'test 3' @store.read(key).length.should.equal 2 end end describe 'Rack::Cache::MetaStore' do describe 'Heap' do before do @store = Rack::Cache::MetaStore::Heap.new @entity_store = Rack::Cache::EntityStore::Heap.new end behaves_like 'A Rack::Cache::MetaStore Implementation' end describe 'Disk' do before do @temp_dir = create_temp_directory @store = Rack::Cache::MetaStore::Disk.new("#{@temp_dir}/meta") @entity_store = Rack::Cache::EntityStore::Disk.new("#{@temp_dir}/entity") end after do remove_entry_secure @temp_dir end behaves_like 'A Rack::Cache::MetaStore Implementation' end need_memcached 'metastore tests' do describe 'MemCached' do before do @temp_dir = create_temp_directory $memcached.flush @store = Rack::Cache::MetaStore::MemCached.new($memcached) @entity_store = Rack::Cache::EntityStore::Heap.new end behaves_like 'A Rack::Cache::MetaStore Implementation' end describe 'options parsing' do before do uri = URI.parse("memcached://#{ENV['MEMCACHED']}/meta_ns1?show_backtraces=true") @memcached_metastore = Rack::Cache::MetaStore::MemCached.resolve uri end it 'passes options from uri' do @memcached_metastore.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true end it 'takes namespace into account' do @memcached_metastore.cache.instance_variable_get(:@options)[:prefix_key].should.equal 'meta_ns1' end end end need_dalli 'metastore tests' do describe 'Dalli' do before do @temp_dir = create_temp_directory $dalli.flush_all @store = Rack::Cache::MetaStore::Dalli.new($dalli) @entity_store = Rack::Cache::EntityStore::Heap.new end behaves_like 'A Rack::Cache::MetaStore Implementation' end describe 'options parsing' do before do uri = URI.parse("memcached://#{ENV['MEMCACHED']}/meta_ns1?show_backtraces=true") @dalli_metastore = Rack::Cache::MetaStore::Dalli.resolve uri end it 'passes options from uri' do @dalli_metastore.cache.instance_variable_get(:@options)[:show_backtraces].should.equal true end it 'takes namespace into account' do @dalli_metastore.cache.instance_variable_get(:@options)[:namespace].should.equal 'meta_ns1' end end end need_java 'entity store testing' do module Rack::Cache::AppEngine module MC class << (Service = {}) def contains(key); include?(key); end def get(key); self[key]; end; def put(key, value, ttl = nil) self[key] = value end end end end describe 'GAEStore' do before :each do Rack::Cache::AppEngine::MC::Service.clear @store = Rack::Cache::MetaStore::GAEStore.new @entity_store = Rack::Cache::EntityStore::Heap.new end behaves_like 'A Rack::Cache::MetaStore Implementation' end end end rack-cache-1.2/test/storage_test.rb0000644000000000000000000000602711745752772016070 0ustar rootrootrequire "#{File.dirname(__FILE__)}/spec_setup" require 'rack/cache/storage' describe 'Rack::Cache::Storage' do before do @storage = Rack::Cache::Storage.new end it "fails when an unknown URI scheme is provided" do lambda { @storage.resolve_metastore_uri('foo:/') }.should.raise end it "creates a new MetaStore for URI if none exists" do @storage.resolve_metastore_uri('heap:/'). should.be.kind_of Rack::Cache::MetaStore end it "returns an existing MetaStore instance for URI that exists" do store = @storage.resolve_metastore_uri('heap:/') @storage.resolve_metastore_uri('heap:/').should.be.same_as store end it "creates a new EntityStore for URI if none exists" do @storage.resolve_entitystore_uri('heap:/'). should.be.kind_of Rack::Cache::EntityStore end it "returns an existing EntityStore instance for URI that exists" do store = @storage.resolve_entitystore_uri('heap:/') @storage.resolve_entitystore_uri('heap:/').should.be.same_as store end it "clears all URI -> store mappings with #clear" do meta = @storage.resolve_metastore_uri('heap:/') entity = @storage.resolve_entitystore_uri('heap:/') @storage.clear @storage.resolve_metastore_uri('heap:/').should.not.be.same_as meta @storage.resolve_entitystore_uri('heap:/').should.not.be.same_as entity end describe 'Heap Store URIs' do %w[heap:/ mem:/].each do |uri| it "resolves #{uri} meta store URIs" do @storage.resolve_metastore_uri(uri). should.be.kind_of Rack::Cache::MetaStore end it "resolves #{uri} entity store URIs" do @storage.resolve_entitystore_uri(uri). should.be.kind_of Rack::Cache::EntityStore end end end describe 'Disk Store URIs' do before do @temp_dir = create_temp_directory end after do remove_entry_secure @temp_dir @temp_dir = nil end %w[file: disk:].each do |uri| it "resolves #{uri} meta store URIs" do @storage.resolve_metastore_uri(uri + @temp_dir). should.be.kind_of Rack::Cache::MetaStore end it "resolves #{uri} entity store URIs" do @storage.resolve_entitystore_uri(uri + @temp_dir). should.be.kind_of Rack::Cache::EntityStore end end end if have_memcached? describe 'MemCache Store URIs' do %w[memcache: memcached:].each do |scheme| it "resolves #{scheme} meta store URIs" do uri = scheme + '//' + ENV['MEMCACHED'] @storage.resolve_metastore_uri(uri). should.be.kind_of Rack::Cache::MetaStore end it "resolves #{scheme} entity store URIs" do uri = scheme + '//' + ENV['MEMCACHED'] @storage.resolve_entitystore_uri(uri). should.be.kind_of Rack::Cache::EntityStore end end it 'supports namespaces in memcached: URIs' do uri = "memcached://" + ENV['MEMCACHED'] + "/namespace" @storage.resolve_metastore_uri(uri). should.be.kind_of Rack::Cache::MetaStore end end end end rack-cache-1.2/test/context_test.rb0000644000000000000000000006730011745752772016111 0ustar rootrootrequire "#{File.dirname(__FILE__)}/spec_setup" require 'rack/cache/context' describe 'Rack::Cache::Context' do before { setup_cache_context } after { teardown_cache_context } it 'passes on non-GET/HEAD requests' do respond_with 200 post '/' app.should.be.called response.should.be.ok cache.trace.should.include :pass response.headers.should.not.include 'Age' end it 'passes on rack-cache.force-pass' do respond_with 200 get '/', {"rack-cache.force-pass" => true} app.should.be.called response.should.be.ok cache.trace.should == [:pass] response.headers.should.not.include 'Age' end %w[post put delete].each do |request_method| it "invalidates on #{request_method} requests" do respond_with 200 request request_method, '/' app.should.be.called response.should.be.ok cache.trace.should.include :invalidate cache.trace.should.include :pass end end it 'does not cache with Authorization request header and non public response' do respond_with 200, 'ETag' => '"FOO"' get '/', 'HTTP_AUTHORIZATION' => 'basic foobarbaz' app.should.be.called response.should.be.ok response.headers['Cache-Control'].should.equal 'private' cache.trace.should.include :miss cache.trace.should.not.include :store response.headers.should.not.include 'Age' end it 'does cache with Authorization request header and public response' do respond_with 200, 'Cache-Control' => 'public', 'ETag' => '"FOO"' get '/', 'HTTP_AUTHORIZATION' => 'basic foobarbaz' app.should.be.called response.should.be.ok cache.trace.should.include :miss cache.trace.should.include :store cache.trace.should.not.include :ignore response.headers.should.include 'Age' response.headers['Cache-Control'].should.equal 'public' end it 'does not cache with Cookie header and non public response' do respond_with 200, 'ETag' => '"FOO"' get '/', 'HTTP_COOKIE' => 'foo=bar' app.should.be.called response.should.be.ok response.headers['Cache-Control'].should.equal 'private' cache.trace.should.include :miss cache.trace.should.not.include :store response.headers.should.not.include 'Age' end it 'does not cache requests with a Cookie header' do respond_with 200 get '/', 'HTTP_COOKIE' => 'foo=bar' response.should.be.ok app.should.be.called cache.trace.should.include :miss cache.trace.should.not.include :store response.headers.should.not.include 'Age' response.headers['Cache-Control'].should.equal 'private' end it 'does remove Set-Cookie response header from a cacheable response' do respond_with 200, 'Cache-Control' => 'public', 'ETag' => '"FOO"', 'Set-Cookie' => 'TestCookie=OK' get '/' app.should.be.called response.should.be.ok cache.trace.should.include :store cache.trace.should.include :ignore response.headers['Set-Cookie'].should.be.nil end it 'does remove all configured ignore_headers from a cacheable response' do respond_with 200, 'Cache-Control' => 'public', 'ETag' => '"FOO"', 'SET-COOKIE' => 'TestCookie=OK', 'X-Strip-Me' => 'Secret' get '/', 'rack-cache.ignore_headers' => ['set-cookie', 'x-strip-me'] app.should.be.called response.should.be.ok cache.trace.should.include :store cache.trace.should.include :ignore response.headers['Set-Cookie'].should.be.nil response.headers['x-strip-me'].should.be.nil end it 'does not remove Set-Cookie response header from a private response' do respond_with 200, 'Cache-Control' => 'private', 'Set-Cookie' => 'TestCookie=OK' get '/' app.should.be.called response.should.be.ok cache.trace.should.not.include :store cache.trace.should.not.include :ignore response.headers['Set-Cookie'].should.equal 'TestCookie=OK' end it 'responds with 304 when If-Modified-Since matches Last-Modified' do timestamp = Time.now.httpdate respond_with do |req,res| res.status = 200 res['Last-Modified'] = timestamp res['Content-Type'] = 'text/plain' res.body = ['Hello World'] end get '/', 'HTTP_IF_MODIFIED_SINCE' => timestamp app.should.be.called response.status.should.equal 304 response.original_headers.should.not.include 'Content-Length' response.original_headers.should.not.include 'Content-Type' response.body.should.empty cache.trace.should.include :miss cache.trace.should.include :store end it 'responds with 304 when If-None-Match matches ETag' do respond_with do |req,res| res.status = 200 res['ETag'] = '12345' res['Content-Type'] = 'text/plain' res.body = ['Hello World'] end get '/', 'HTTP_IF_NONE_MATCH' => '12345' app.should.be.called response.status.should.equal 304 response.original_headers.should.not.include 'Content-Length' response.original_headers.should.not.include 'Content-Type' response.headers.should.include 'ETag' response.body.should.empty cache.trace.should.include :miss cache.trace.should.include :store end it 'responds with 304 only if If-None-Match and If-Modified-Since both match' do timestamp = Time.now respond_with do |req,res| res.status = 200 res['ETag'] = '12345' res['Last-Modified'] = timestamp.httpdate res['Content-Type'] = 'text/plain' res.body = ['Hello World'] end # Only etag matches get '/', 'HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => (timestamp - 1).httpdate app.should.be.called response.status.should.equal 200 # Only last-modified matches get '/', 'HTTP_IF_NONE_MATCH' => '1234', 'HTTP_IF_MODIFIED_SINCE' => timestamp.httpdate app.should.be.called response.status.should.equal 200 # Both matches get '/', 'HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => timestamp.httpdate app.should.be.called response.status.should.equal 304 end it 'validates private responses cached on the client' do respond_with do |req,res| etags = req.env['HTTP_IF_NONE_MATCH'].to_s.split(/\s*,\s*/) if req.env['HTTP_COOKIE'] == 'authenticated' res['Cache-Control'] = 'private, no-store' res['ETag'] = '"private tag"' if etags.include?('"private tag"') res.status = 304 else res.status = 200 res['Content-Type'] = 'text/plain' res.body = ['private data'] end else res['ETag'] = '"public tag"' if etags.include?('"public tag"') res.status = 304 else res.status = 200 res['Content-Type'] = 'text/plain' res.body = ['public data'] end end end get '/' app.should.be.called response.status.should.equal 200 response.headers['ETag'].should == '"public tag"' response.body.should == 'public data' cache.trace.should.include :miss cache.trace.should.include :store get '/', 'HTTP_COOKIE' => 'authenticated' app.should.be.called response.status.should.equal 200 response.headers['ETag'].should == '"private tag"' response.body.should == 'private data' cache.trace.should.include :stale cache.trace.should.include :invalid cache.trace.should.not.include :store get '/', 'HTTP_IF_NONE_MATCH' => '"public tag"' app.should.be.called response.status.should.equal 304 response.headers['ETag'].should == '"public tag"' cache.trace.should.include :stale cache.trace.should.include :valid cache.trace.should.include :store get '/', 'HTTP_IF_NONE_MATCH' => '"private tag"', 'HTTP_COOKIE' => 'authenticated' app.should.be.called response.status.should.equal 304 response.headers['ETag'].should == '"private tag"' cache.trace.should.include :valid cache.trace.should.not.include :store end it 'stores responses when no-cache request directive present' do respond_with 200, 'Expires' => (Time.now + 5).httpdate get '/', 'HTTP_CACHE_CONTROL' => 'no-cache' response.should.be.ok cache.trace.should.include :store response.headers.should.include 'Age' end it 'reloads responses when cache hits but no-cache request directive present ' + 'when allow_reload is set true' do count = 0 respond_with 200, 'Cache-Control' => 'max-age=10000' do |req,res| count+= 1 res.body = (count == 1) ? ['Hello World'] : ['Goodbye World'] end get '/' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :store get '/' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :fresh get '/', 'rack-cache.allow_reload' => true, 'HTTP_CACHE_CONTROL' => 'no-cache' response.should.be.ok response.body.should.equal 'Goodbye World' cache.trace.should.include :reload cache.trace.should.include :store end it 'does not reload responses when allow_reload is set false (default)' do count = 0 respond_with 200, 'Cache-Control' => 'max-age=10000' do |req,res| count+= 1 res.body = (count == 1) ? ['Hello World'] : ['Goodbye World'] end get '/' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :store get '/' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :fresh get '/', 'rack-cache.allow_reload' => false, 'HTTP_CACHE_CONTROL' => 'no-cache' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.not.include :reload # test again without explicitly setting the allow_reload option to false get '/', 'HTTP_CACHE_CONTROL' => 'no-cache' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.not.include :reload end it 'revalidates fresh cache entry when max-age request directive is exceeded ' + 'when allow_revalidate option is set true' do count = 0 respond_with do |req,res| count+= 1 res['Cache-Control'] = 'max-age=10000' res['ETag'] = count.to_s res.body = (count == 1) ? ['Hello World'] : ['Goodbye World'] end get '/' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :store get '/' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :fresh get '/', 'rack-cache.allow_revalidate' => true, 'HTTP_CACHE_CONTROL' => 'max-age=0' response.should.be.ok response.body.should.equal 'Goodbye World' cache.trace.should.include :stale cache.trace.should.include :invalid cache.trace.should.include :store end it 'does not revalidate fresh cache entry when enable_revalidate option is set false (default)' do count = 0 respond_with do |req,res| count+= 1 res['Cache-Control'] = 'max-age=10000' res['ETag'] = count.to_s res.body = (count == 1) ? ['Hello World'] : ['Goodbye World'] end get '/' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :store get '/' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :fresh get '/', 'rack-cache.allow_revalidate' => false, 'HTTP_CACHE_CONTROL' => 'max-age=0' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.not.include :stale cache.trace.should.not.include :invalid cache.trace.should.include :fresh # test again without explicitly setting the allow_revalidate option to false get '/', 'HTTP_CACHE_CONTROL' => 'max-age=0' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.not.include :stale cache.trace.should.not.include :invalid cache.trace.should.include :fresh end it 'fetches response from backend when cache misses' do respond_with 200, 'Expires' => (Time.now + 5).httpdate get '/' response.should.be.ok cache.trace.should.include :miss response.headers.should.include 'Age' end [(201..202),(204..206),(303..305),(400..403),(405..409),(411..417),(500..505)].each do |range| range.each do |response_code| it "does not cache #{response_code} responses" do respond_with response_code, 'Expires' => (Time.now + 5).httpdate get '/' cache.trace.should.not.include :store response.status.should.equal response_code response.headers.should.not.include 'Age' end end end it "does not cache responses with explicit no-store directive" do respond_with 200, 'Expires' => (Time.now + 5).httpdate, 'Cache-Control' => 'no-store' get '/' response.should.be.ok cache.trace.should.not.include :store response.headers.should.not.include 'Age' end it 'does not cache responses without freshness information or a validator' do respond_with 200 get '/' response.should.be.ok cache.trace.should.not.include :store end it "caches responses with explicit no-cache directive" do respond_with 200, 'Expires' => (Time.now + 5).httpdate, 'Cache-Control' => 'no-cache' get '/' response.should.be.ok cache.trace.should.include :store response.headers.should.include 'Age' end it 'caches responses with an Expiration header' do respond_with 200, 'Expires' => (Time.now + 5).httpdate get '/' response.should.be.ok response.body.should.equal 'Hello World' response.headers.should.include 'Date' response['Age'].should.not.be.nil response['X-Content-Digest'].should.not.be.nil cache.trace.should.include :miss cache.trace.should.include :store cache.metastore.to_hash.keys.length.should.equal 1 end it 'caches responses with a max-age directive' do respond_with 200, 'Cache-Control' => 'max-age=5' get '/' response.should.be.ok response.body.should.equal 'Hello World' response.headers.should.include 'Date' response['Age'].should.not.be.nil response['X-Content-Digest'].should.not.be.nil cache.trace.should.include :miss cache.trace.should.include :store cache.metastore.to_hash.keys.length.should.equal 1 end it 'caches responses with a s-maxage directive' do respond_with 200, 'Cache-Control' => 's-maxage=5' get '/' response.should.be.ok response.body.should.equal 'Hello World' response.headers.should.include 'Date' response['Age'].should.not.be.nil response['X-Content-Digest'].should.not.be.nil cache.trace.should.include :miss cache.trace.should.include :store cache.metastore.to_hash.keys.length.should.equal 1 end it 'caches responses with a Last-Modified validator but no freshness information' do respond_with 200, 'Last-Modified' => Time.now.httpdate get '/' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :miss cache.trace.should.include :store end it 'caches responses with an ETag validator but no freshness information' do respond_with 200, 'ETag' => '"123456"' get '/' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :miss cache.trace.should.include :store end it 'hits cached response with Expires header' do respond_with 200, 'Date' => (Time.now - 5).httpdate, 'Expires' => (Time.now + 5).httpdate get '/' app.should.be.called response.should.be.ok response.headers.should.include 'Date' cache.trace.should.include :miss cache.trace.should.include :store response.body.should.equal 'Hello World' get '/' response.should.be.ok app.should.not.be.called response['Date'].should.equal responses.first['Date'] response['Age'].to_i.should.satisfy { |age| age > 0 } response['X-Content-Digest'].should.not.be.nil cache.trace.should.include :fresh cache.trace.should.not.include :store response.body.should.equal 'Hello World' end it 'hits cached response with max-age directive' do respond_with 200, 'Date' => (Time.now - 5).httpdate, 'Cache-Control' => 'max-age=10' get '/' app.should.be.called response.should.be.ok response.headers.should.include 'Date' cache.trace.should.include :miss cache.trace.should.include :store response.body.should.equal 'Hello World' get '/' response.should.be.ok app.should.not.be.called response['Date'].should.equal responses.first['Date'] response['Age'].to_i.should.satisfy { |age| age > 0 } response['X-Content-Digest'].should.not.be.nil cache.trace.should.include :fresh cache.trace.should.not.include :store response.body.should.equal 'Hello World' end it 'hits cached response with s-maxage directive' do respond_with 200, 'Date' => (Time.now - 5).httpdate, 'Cache-Control' => 's-maxage=10, max-age=0' get '/' app.should.be.called response.should.be.ok response.headers.should.include 'Date' cache.trace.should.include :miss cache.trace.should.include :store response.body.should.equal 'Hello World' get '/' response.should.be.ok app.should.not.be.called response['Date'].should.equal responses.first['Date'] response['Age'].to_i.should.satisfy { |age| age > 0 } response['X-Content-Digest'].should.not.be.nil cache.trace.should.include :fresh cache.trace.should.not.include :store response.body.should.equal 'Hello World' end it 'assigns default_ttl when response has no freshness information' do respond_with 200 get '/', 'rack-cache.default_ttl' => 10 app.should.be.called response.should.be.ok cache.trace.should.include :miss cache.trace.should.include :store response.body.should.equal 'Hello World' response['Cache-Control'].should.include 's-maxage=10' get '/', 'rack-cache.default_ttl' => 10 response.should.be.ok app.should.not.be.called cache.trace.should.include :fresh cache.trace.should.not.include :store response.body.should.equal 'Hello World' end it 'does not assign default_ttl when response has must-revalidate directive' do respond_with 200, 'Cache-Control' => 'must-revalidate' get '/', 'rack-cache.default_ttl' => 10 app.should.be.called response.should.be.ok cache.trace.should.include :miss cache.trace.should.not.include :store response['Cache-Control'].should.not.include 's-maxage' response.body.should.equal 'Hello World' end it 'fetches full response when cache stale and no validators present' do respond_with 200, 'Expires' => (Time.now + 5).httpdate # build initial request get '/' app.should.be.called response.should.be.ok response.headers.should.include 'Date' response.headers.should.include 'X-Content-Digest' response.headers.should.include 'Age' cache.trace.should.include :miss cache.trace.should.include :store response.body.should.equal 'Hello World' # go in and play around with the cached metadata directly ... # XXX find some other way to do this hash = cache.metastore.to_hash hash.values.length.should.equal 1 entries = Marshal.load(hash.values.first) entries.length.should.equal 1 req, res = entries.first res['Expires'] = (Time.now - 1).httpdate hash[hash.keys.first] = Marshal.dump([[req, res]]) # build subsequent request; should be found but miss due to freshness get '/' app.should.be.called response.should.be.ok response['Age'].to_i.should.equal 0 response.headers.should.include 'X-Content-Digest' cache.trace.should.include :stale cache.trace.should.not.include :fresh cache.trace.should.not.include :miss cache.trace.should.include :store response.body.should.equal 'Hello World' end it 'validates cached responses with Last-Modified and no freshness information' do timestamp = Time.now.httpdate respond_with do |req,res| res['Last-Modified'] = timestamp if req.env['HTTP_IF_MODIFIED_SINCE'] == timestamp res.status = 304 res.body = [] end end # build initial request get '/' app.should.be.called response.should.be.ok response.headers.should.include 'Last-Modified' response.headers.should.include 'X-Content-Digest' response.body.should.equal 'Hello World' cache.trace.should.include :miss cache.trace.should.include :store cache.trace.should.not.include :stale # build subsequent request; should be found but miss due to freshness get '/' app.should.be.called response.should.be.ok response.headers.should.include 'Last-Modified' response.headers.should.include 'X-Content-Digest' response['Age'].to_i.should.equal 0 response.body.should.equal 'Hello World' cache.trace.should.include :stale cache.trace.should.include :valid cache.trace.should.include :store cache.trace.should.not.include :miss end it 'validates cached responses with ETag and no freshness information' do timestamp = Time.now.httpdate respond_with do |req,res| res['ETAG'] = '"12345"' if req.env['HTTP_IF_NONE_MATCH'] == res['Etag'] res.status = 304 res.body = [] end end # build initial request get '/' app.should.be.called response.should.be.ok response.headers.should.include 'ETag' response.headers.should.include 'X-Content-Digest' response.body.should.equal 'Hello World' cache.trace.should.include :miss cache.trace.should.include :store # build subsequent request; should be found but miss due to freshness get '/' app.should.be.called response.should.be.ok response.headers.should.include 'ETag' response.headers.should.include 'X-Content-Digest' response['Age'].to_i.should.equal 0 response.body.should.equal 'Hello World' cache.trace.should.include :stale cache.trace.should.include :valid cache.trace.should.include :store cache.trace.should.not.include :miss end it 'replaces cached responses when validation results in non-304 response' do timestamp = Time.now.httpdate count = 0 respond_with do |req,res| res['Last-Modified'] = timestamp case (count+=1) when 1 ; res.body = ['first response'] when 2 ; res.body = ['second response'] when 3 res.body = [] res.status = 304 end end # first request should fetch from backend and store in cache get '/' response.status.should.equal 200 response.body.should.equal 'first response' # second request is validated, is invalid, and replaces cached entry get '/' response.status.should.equal 200 response.body.should.equal 'second response' # third respone is validated, valid, and returns cached entry get '/' response.status.should.equal 200 response.body.should.equal 'second response' count.should.equal 3 end it 'passes HEAD requests through directly on pass' do respond_with do |req,res| res.status = 200 res.body = [] req.request_method.should.equal 'HEAD' end head '/', 'HTTP_EXPECT' => 'something ...' app.should.be.called response.body.should.equal '' end it 'uses cache to respond to HEAD requests when fresh' do respond_with do |req,res| res['Cache-Control'] = 'max-age=10' res.body = ['Hello World'] req.request_method.should.not.equal 'HEAD' end get '/' app.should.be.called response.status.should.equal 200 response.body.should.equal 'Hello World' head '/' app.should.not.be.called response.status.should.equal 200 response.body.should.equal '' response['Content-Length'].should.equal 'Hello World'.length.to_s end it 'invalidates cached responses on POST' do respond_with do |req,res| if req.request_method == 'GET' res.status = 200 res['Cache-Control'] = 'public, max-age=500' res.body = ['Hello World'] elsif req.request_method == 'POST' res.status = 303 res['Location'] = '/' res.headers.delete('Cache-Control') res.body = [] end end # build initial request to enter into the cache get '/' app.should.be.called response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :miss cache.trace.should.include :store # make sure it is valid get '/' app.should.not.called response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :fresh # now POST to same URL post '/' app.should.be.called response.should.be.redirect response['Location'].should.equal '/' cache.trace.should.include :invalidate cache.trace.should.include :pass response.body.should.equal '' # now make sure it was actually invalidated get '/' app.should.be.called response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :stale cache.trace.should.include :invalid cache.trace.should.include :store end describe 'with responses that include a Vary header' do before do count = 0 respond_with 200 do |req,res| res['Vary'] = 'Accept User-Agent Foo' res['Cache-Control'] = 'max-age=10' res['X-Response-Count'] = (count+=1).to_s res.body = [req.env['HTTP_USER_AGENT']] end end it 'serves from cache when headers match' do get '/', 'HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0' response.should.be.ok response.body.should.equal 'Bob/1.0' cache.trace.should.include :miss cache.trace.should.include :store get '/', 'HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0' response.should.be.ok response.body.should.equal 'Bob/1.0' cache.trace.should.include :fresh cache.trace.should.not.include :store response.headers.should.include 'X-Content-Digest' end it 'stores multiple responses when headers differ' do get '/', 'HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0' response.should.be.ok response.body.should.equal 'Bob/1.0' response['X-Response-Count'].should.equal '1' get '/', 'HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0' cache.trace.should.include :miss cache.trace.should.include :store response.body.should.equal 'Bob/2.0' response['X-Response-Count'].should.equal '2' get '/', 'HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0' cache.trace.should.include :fresh response.body.should.equal 'Bob/1.0' response['X-Response-Count'].should.equal '1' get '/', 'HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0' cache.trace.should.include :fresh response.body.should.equal 'Bob/2.0' response['X-Response-Count'].should.equal '2' get '/', 'HTTP_USER_AGENT' => 'Bob/2.0' cache.trace.should.include :miss response.body.should.equal 'Bob/2.0' response['X-Response-Count'].should.equal '3' end end it 'passes if there was a metastore exception' do respond_with 200, 'Cache-Control' => 'max-age=10000' do |req,res| res.body = ['Hello World'] end get '/' response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :store get '/' do |cache| cache.meta_def(:metastore) { raise Timeout::Error } end response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :pass post '/' do |cache| cache.meta_def(:metastore) { raise Timeout::Error } end response.should.be.ok response.body.should.equal 'Hello World' cache.trace.should.include :pass end end rack-cache-1.2/Rakefile0000644000000000000000000001014311745752772013520 0ustar rootrootrequire 'rake/clean' task :default => [:setup, :test] CLEAN.include %w[coverage/ doc/api tags] CLOBBER.include %w[dist] desc "Install gem dependencies" task :setup do sh "bundle check >/dev/null || bundle install", :verbose => false end # SPECS ===================================================================== desc 'Run specs with unit test style output' task :test => FileList['test/*_test.rb'] do |t| suite = t.prerequisites sh "bundle exec bacon -q -I.:lib:test #{suite.join(' ')}", :verbose => false end desc 'Run specs with story style output' task :spec => FileList['test/*_test.rb'] do |t| suite = t.prerequisites sh "bundle exec bacon -I.:lib:test #{suite.join(' ')}", :verbose => false end desc 'Generate test coverage report' task :rcov do sh "rcov -I.:lib:test test/*_test.rb" end # DOC ======================================================================= desc 'Build all documentation' task :doc => %w[doc:api doc:markdown] # requires the hanna gem: # gem install mislav-hanna --source=http://gems.github.com desc 'Build API documentation (doc/api)' task 'doc:api' => 'doc/api/index.html' file 'doc/api/index.html' => FileList['lib/**/*.rb'] do |f| rm_rf 'doc/api' sh((<<-SH).gsub(/[\s\n]+/, ' ').strip) hanna --op doc/api --promiscuous --charset utf8 --fmt html --inline-source --line-numbers --accessor option_accessor=RW --main Rack::Cache --title 'Rack::Cache API Documentation' #{f.prerequisites.join(' ')} SH end CLEAN.include 'doc/api' desc 'Build markdown documentation files' task 'doc:markdown' FileList['doc/*.markdown'].each do |source| dest = "doc/#{File.basename(source, '.markdown')}.html" file dest => [source, 'doc/layout.html.erb'] do |f| puts "markdown: #{source} -> #{dest}" if verbose require 'erb' unless defined? ERB require 'rdiscount' unless defined? RDiscount template = File.read(source) content = Markdown.new(ERB.new(template, 0, "%<>").result(binding), :smart).to_html title = content.match("

(.*)

")[1] rescue '' layout = ERB.new(File.read("doc/layout.html.erb"), 0, "%<>") output = layout.result(binding) File.open(dest, 'w') { |io| io.write(output) } end task 'doc:markdown' => dest CLEAN.include dest end desc 'Publish documentation' task 'doc:publish' => :doc do sh 'rsync -avz doc/ gus@tomayko.com:/src/rack-cache' end desc 'Start the documentation development server (requires thin)' task 'doc:server' do sh 'cd doc && thin --rackup server.ru --port 3035 start' end # PACKAGING ================================================================= if defined?(Gem) # load gemspec $spec = eval(File.read('rack-cache.gemspec')) def package(ext='') "dist/rack-cache-#{$spec.version}" + ext end desc 'Build packages' task :package => %w[.gem .tar.gz].map {|e| package(e)} desc 'Build and install as local gem' task :install => package('.gem') do sh "gem install #{package('.gem')}" end directory 'dist/' file package('.gem') => %w[dist/ rack-cache.gemspec] + $spec.files do |f| sh "gem build rack-cache.gemspec" mv File.basename(f.name), f.name end file package('.tar.gz') => %w[dist/] + $spec.files do |f| sh "git archive --format=tar HEAD | gzip > #{f.name}" end desc 'Upload gem to gemcutter.org' task 'release' => [package('.gem')] do |t| sh "gem push #{package('.gem')}" end end # GEMSPEC =================================================================== file 'rack-cache.gemspec' => FileList['{lib,test}/**','Rakefile'] do |f| # read spec file and split out manifest section spec = File.read(f.name) parts = spec.split(" # = MANIFEST =\n") fail 'bad spec' if parts.length != 3 # determine file list from git ls-files files = `git ls-files`. split("\n").sort.reject{ |file| file =~ /^\./ }. map{ |file| " #{file}" }.join("\n") # piece file back together and write... parts[1] = " s.files = %w[\n#{files}\n ]\n" spec = parts.join(" # = MANIFEST =\n") spec.sub!(/s.date = '.*'/, "s.date = '#{Time.now.strftime("%Y-%m-%d")}'") File.open(f.name, 'w') { |io| io.write(spec) } puts "updated #{f.name}" end rack-cache-1.2/example/0000755000000000000000000000000011745752772013507 5ustar rootrootrack-cache-1.2/example/sinatra/0000755000000000000000000000000011745752772015150 5ustar rootrootrack-cache-1.2/example/sinatra/views/0000755000000000000000000000000011745752772016305 5ustar rootrootrack-cache-1.2/example/sinatra/views/index.erb0000644000000000000000000000165511745752772020115 0ustar rootroot Sample Rack::Cache Sinatra app

Last updated at: <%= $updated_at.strftime('%l:%m:%S%P') %>

Headers:

<% response.headers.each do |key, value| %>

<%= key %>: <%= value %>

<% end %>

Params:

<% params.each do |key, value| %>

<%= key %>: <%= value || '(blank)' %>

<% end %>
rack-cache-1.2/example/sinatra/app.rb0000644000000000000000000000042611745752772016257 0ustar rootrootrequire 'sinatra' require 'rack/cache' use Rack::Cache do set :verbose, true set :metastore, 'heap:/' set :entitystore, 'heap:/' end before do last_modified $updated_at ||= Time.now end get '/' do erb :index end put '/' do $updated_at = nil redirect '/' end rack-cache-1.2/Gemfile0000644000000000000000000000003111745752772013341 0ustar rootrootsource :rubygems gemspec rack-cache-1.2/TODO0000644000000000000000000000235411745752772012550 0ustar rootroot## 0.5 - Document allow_revalidate and allow_reload options. - Support multiple memcache servers. - Purge/invalidate everything - Explicit expiration/invalidation based on response headers or via an object interface passed in the rack env. - Sample apps: Rack, Rails, Sinatra, Merb, etc. - Move old breakers.rb configuration file into rack-contrib as a middleware component. ## Backlog - Use Bacon instead of test/spec - Fast path pass processing. We do a lot more than necessary just to determine that the response should be passed through untouched. - Invalidate at the URI of the Location or Content-Location response header on POST, PUT, or DELETE that results in a redirect. - Maximum size of cached entity - Last-Modified factor: requests that have a Last-Modified header but no Expires header have a TTL assigned based on the last modified age of the response: TTL = (Age * Factor), or, 1h = (10h * 0.1) - Consider implementing ESI (http://www.w3.org/TR/esi-lang). This should probably be implemented as a separate middleware component. - stale-while-revalidate - Serve cached copies when down (see: stale-if-error) - e.g., database connection drops and the cache takes over what it can. rack-cache-1.2/CHANGES0000644000000000000000000002431111745752772013050 0ustar rootroot## 1.2 / March 2012 * Fix a cookie leak vulnerability effecting large numbers of Rails 3.x installs: https://github.com/rtomayko/rack-cache/pull/52 * Never 304 on PUT or POST requests. * Misc bundler and test tooling fixes. ## 1.1 / September 2011 * Allow (INM/IMS) validation requests through to backend on miss. Makes it possible to use validation for private / uncacheable responses. A number of people using Rails's stale?() helper reported that their validation logic was never kicking in. * Add rack env rack-cache.force-pass option to bypass rack-cache on per request basis * Fix an issue with memcache namespace not being set when using the :namespace option instead of :prefix_key. * Fix test failures due to MockResponse changes in recent Rack version (issue #34) ## 1.0.3 / August 2011 * Fix bug passing options to memcached and dalli * Document cache_key ## 1.0.1 / April 2011 * Added lib/rack-cache.rb to match package name for auto-requiring machinery. * Fixed a number of issues caused by Rack::Cache not closing the body received from the application. Rack::Lock and other middleware use body.close to signal the true end of request processing so failure to call this method can result in strange issues (e.g., "ThreadError: deadlock; recursive locking") * Fixed a bug where Rack::Cache would blow up writing the rack env to the meta store when the env contained an all uppercase key whose value wasn't marshalable. Passenger and some other stuff write such keys apparently. * The test suite has moved from test-spec to bacon. This is a short term solution to the problem of not being able to run tests under Ruby 1.9.x. The test suite will be moved to basic Test::Unit style sometime in the future. ## 1.0 / December 2010 * Rack::Cache is 1.0 and will now maintain semantic versioning * Add Dalli memcache client support and removed support for the unmaintained memcache-client library. You will need to move your apps to Dalli before upgrading rack-cache to 1.0. ## 0.5.3 / September 2010 * A matching If-Modified-Since is ignored if an If-None-Match is also provided and doesn't match. This is in line with RFC 2616. * Converts string status codes to integers before returns to workaround bad behaving rack middleware and apps. * Misc doc clean up. ## 0.5.2 / September 2009 * Exceptions raised from the metastore are not fatal. This makes a lot of sense in most cases because its okay for the cache to be down - it shouldn't blow up your app. ## 0.5.1 / June 2009 * Added support for memcached clusters and other advanced configuration provided by the memcache-client and memcached libraries. The "metastore" and "entitystore" options can now be set to a MemCache object or Memcached object: memcache = MemCache.new(['127.1.1.1', '127.1.1.2'], :namespace => "/foo") use Rack::Cache, :metastore => memcache, :entitystore => memcache * Fix "memcached://" metastore URL handling. The "memcached" variation blew up, the "memcache" version was fine. ## 0.5.0 / May 2009 * Added meta and entity store implementations based on the memcache-client library. These are the default unless the memcached library has already been required. * The "allow_reload" and "allow_revalidate" options now default to false instead of true. This means we break with RFC 2616 out of the box but this is the expected configuration in a huge majority of gateway cache scenarios. See the docs on configuration options for more information on these options: http://tomayko.com/src/rack-cache/configuration * Added Google AppEngine memcache entity store and metastore implementations. To use GAE's memcache with rack-cache, set the "metastore" and "entitystore" options as follows: use Rack::Cache, :metastore => 'gae://cache-meta', :entitystore => 'gae://cache-body' The 'cache-meta' and 'cache-body' parts are memcache namespace prefixes and should be set to different values. ## 0.4.0 / March 2009 * Ruby 1.9.1 / Rack 1.0 compatible. * Invalidate cache entries that match the request URL on non-GET/HEAD requests. i.e., POST, PUT, DELETE cause matching cache entries to be invalidated. The cache entry is validated with the backend using a conditional GET the next time it's requested. * Implement "Cache-Control: max-age=N" request directive by forcing validation when the max-age provided exceeds the age of the cache entry. This can be disabled by setting the "allow_revalidate" option to false. * Properly implement "Cache-Control: no-cache" request directive by performing a full reload. RFC 2616 states that when "no-cache" is present in the request, the cache MUST NOT serve a stored response even after successful validation. This is slightly different from the "no-cache" directive in responses, which indicates that the cache must first validate its entry with the origin. Previously, we implemented "no-cache" on requests by passing so no new cache entry would be stored based on the response. Now we treat it as a forced miss and enter the response into the cache if it's cacheable. This can be disabled by setting the "allow_reload" option to false. * Assume identical semantics for the "Pragma: no-cache" request header as the "Cache-Control: no-cache" directive described above. * Less crazy logging. When the verbose option is set, a single log entry is written with a comma separated list of trace events. For example, if the cache was stale but validated, the following log entry would be written: "cache: stale, valid, store". When the verbose option is false, no logging occurs. * Added "X-Rack-Cache" response header with the same comma separated trace value as described above. This gives some visibility into how the cache processed the request. * Add support for canonicalized cache keys, as well as custom cache key generators, which are specified in the options as :cache_key as either any object that has a call() or as a block. Cache key generators get passed a request object and return a cache key string. ## 0.3.0 / December 2008 * Add support for public and private cache control directives. Responses marked as explicitly public are cached even when the request includes an Authorization or Cookie header. Responses marked as explicitly private are considered uncacheable. * Added a "private_headers" option that dictates which request headers trigger default "private" cache control processing. By default, the Cookie and Authorization headers are included. Headers may be added or removed as necessary to change the default private logic. * Adhere to must-revalidate/proxy-revalidate cache control directives by not assigning the default_ttl to responses that don't include freshness information. This should let us begin using default_ttl more liberally since we can control it using the must-revalidate/proxy-revalidate directives. * Use the s-maxage Cache-Control value in preference to max-age when present. The ttl= method now sets the s-maxage value instead of max-age. Code that used ttl= to control freshness at the client needs to change to set the max-age directive explicitly. * Enable support for X-Sendfile middleware by responding to #to_path on bodies served from disk storage. Adding the Rack::Sendfile component upstream from Rack::Cache will result in cached bodies being served directly by the web server (instead of being read in Ruby). * BUG: MetaStore hits but EntityStore misses. This would 500 previously; now we detect it and act as if the MetaStore missed as well. * Implement low level #purge method on all concrete entity store classes -- removes the entity body corresponding to the SHA1 key provided and returns nil. * Basically sane handling of HEAD requests. A HEAD request is never passed through to the backend except when transitioning with pass!. This means that the cache responds to HEAD requests without invoking the backend at all when the cached entry is fresh. When no cache entry exists, or the cached entry is stale and can be validated, the backend is invoked with a GET request and the HEAD is handled right before the response is delivered upstream. * BUG: The Age response header was not being set properly when a stale entry was validated. This would result in Age values that exceeded the freshness lifetime in responses. * BUG: A cached entry in a heap meta store could be unintentionally modified by request processing since the cached objects were being returned directly. The result was typically missing/incorrect header values (e.g., missing Content-Type header). [dkubb] * BUG: 304 responses should not include entity headers (especially Content-Length). This is causing Safari/WebKit weirdness on 304 responses. * BUG: The If-None-Match header was being ignored, causing the cache to send 200 responses to matching conditional GET requests. ## 0.2.0 / 2008-10-24 / Initial Release * Document events and transitions in `rack/cache/config/default.rb` * Basic logging support (`trace`, `warn`, `info`, `error` from within Context) * EntityStore: store entity bodies keyed by SHA * MetaStore: store response headers keyed by URL * Last-Modified/ETag validation * Vary support * Implement error! transition * New Rack::Cache::Core * memcached meta and entity store implementations * URI based storage configuration * Read options from Rack env if present (rack-cache.XXX keys) * `object` is now `entry` * Documentation framework and website * Document storage areas and implementations * Document configuration/events ## 0.1.0 / 2008-07-21 / Proof of concept (unreleased) * Basic core with event support * `#import` method for bringing in config files * Freshness based expiration * RFC 2616 If-Modified-Since based validation * A horribly shitty storage back-end (Hash in mem) * Don't cache hop-by-hop headers: Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailers, Transfer-Encoding, Upgrade rack-cache-1.2/lib/0000755000000000000000000000000011745752772012622 5ustar rootrootrack-cache-1.2/lib/rack/0000755000000000000000000000000011745752772013542 5ustar rootrootrack-cache-1.2/lib/rack/cache.rb0000644000000000000000000000313611745752772015135 0ustar rootrootrequire 'rack' # = HTTP Caching For Rack # # Rack::Cache is suitable as a quick, drop-in component to enable HTTP caching # for Rack-enabled applications that produce freshness (+Expires+, +Cache-Control+) # and/or validation (+Last-Modified+, +ETag+) information. # # * Standards-based (RFC 2616 compliance) # * Freshness/expiration based caching and validation # * Supports HTTP Vary # * Portable: 100% Ruby / works with any Rack-enabled framework # * Disk, memcached, and heap memory storage backends # # === Usage # # Create with default options: # require 'rack/cache' # Rack::Cache.new(app, :verbose => true, :entitystore => 'file:cache') # # Within a rackup file (or with Rack::Builder): # require 'rack/cache' # use Rack::Cache do # set :verbose, true # set :metastore, 'memcached://localhost:11211/meta' # set :entitystore, 'file:/var/cache/rack' # end # run app module Rack::Cache autoload :Request, 'rack/cache/request' autoload :Response, 'rack/cache/response' autoload :Context, 'rack/cache/context' autoload :Storage, 'rack/cache/storage' autoload :CacheControl, 'rack/cache/cachecontrol' # Create a new Rack::Cache middleware component that fetches resources from # the specified backend application. The +options+ Hash can be used to # specify default configuration values (see attributes defined in # Rack::Cache::Options for possible key/values). When a block is given, it # is executed within the context of the newly create Rack::Cache::Context # object. def self.new(backend, options={}, &b) Context.new(backend, options, &b) end end rack-cache-1.2/lib/rack/cache/0000755000000000000000000000000011745752772014605 5ustar rootrootrack-cache-1.2/lib/rack/cache/metastore.rb0000644000000000000000000002616411745752772017146 0ustar rootrootrequire 'fileutils' require 'digest/sha1' require 'rack/utils' require 'rack/cache/key' module Rack::Cache # The MetaStore is responsible for storing meta information about a # request/response pair keyed by the request's URL. # # The meta store keeps a list of request/response pairs for each canonical # request URL. A request/response pair is a two element Array of the form: # [request, response] # # The +request+ element is a Hash of Rack environment keys. Only protocol # keys (i.e., those that start with "HTTP_") are stored. The +response+ # element is a Hash of cached HTTP response headers for the paired request. # # The MetaStore class is abstract and should not be instanstiated # directly. Concrete subclasses should implement the protected #read, # #write, and #purge methods. Care has been taken to keep these low-level # methods dumb and straight-forward to implement. class MetaStore # Locate a cached response for the request provided. Returns a # Rack::Cache::Response object if the cache hits or nil if no cache entry # was found. def lookup(request, entity_store) key = cache_key(request) entries = read(key) # bail out if we have nothing cached return nil if entries.empty? # find a cached entry that matches the request. env = request.env match = entries.detect{|req,res| requests_match?(res['Vary'], env, req)} return nil if match.nil? _, res = match if body = entity_store.open(res['X-Content-Digest']) restore_response(res, body) else # TODO the metastore referenced an entity that doesn't exist in # the entitystore. we definitely want to return nil but we should # also purge the entry from the meta-store when this is detected. end end # Write a cache entry to the store under the given key. Existing # entries are read and any that match the response are removed. # This method calls #write with the new list of cache entries. def store(request, response, entity_store) key = cache_key(request) stored_env = persist_request(request) # write the response body to the entity store if this is the # original response. if response.headers['X-Content-Digest'].nil? if request.env['rack-cache.use_native_ttl'] && response.fresh? digest, size = entity_store.write(response.body, response.ttl) else digest, size = entity_store.write(response.body) end response.headers['X-Content-Digest'] = digest response.headers['Content-Length'] = size.to_s unless response.headers['Transfer-Encoding'] response.body = entity_store.open(digest) end # read existing cache entries, remove non-varying, and add this one to # the list vary = response.vary entries = read(key).reject do |env,res| (vary == res['Vary']) && requests_match?(vary, env, stored_env) end headers = persist_response(response) headers.delete 'Age' entries.unshift [stored_env, headers] write key, entries key end # Generate a cache key for the request. def cache_key(request) keygen = request.env['rack-cache.cache_key'] || Key keygen.call(request) end # Invalidate all cache entries that match the request. def invalidate(request, entity_store) modified = false key = cache_key(request) entries = read(key).map do |req, res| response = restore_response(res) if response.fresh? response.expire! modified = true [req, persist_response(response)] else [req, res] end end write key, entries if modified end private # Extract the environment Hash from +request+ while making any # necessary modifications in preparation for persistence. The Hash # returned must be marshalable. def persist_request(request) env = request.env.dup env.reject! { |key,val| key =~ /[^0-9A-Z_]/ || !val.respond_to?(:to_str) } env end # Converts a stored response hash into a Response object. The caller # is responsible for loading and passing the body if needed. def restore_response(hash, body=nil) status = hash.delete('X-Status').to_i Rack::Cache::Response.new(status, hash, body) end def persist_response(response) hash = response.headers.to_hash hash['X-Status'] = response.status.to_s hash end # Determine whether the two environment hashes are non-varying based on # the vary response header value provided. def requests_match?(vary, env1, env2) return true if vary.nil? || vary == '' vary.split(/[\s,]+/).all? do |header| key = "HTTP_#{header.upcase.tr('-', '_')}" env1[key] == env2[key] end end protected # Locate all cached request/response pairs that match the specified # URL key. The result must be an Array of all cached request/response # pairs. An empty Array must be returned if nothing is cached for # the specified key. def read(key) raise NotImplemented end # Store an Array of request/response pairs for the given key. Concrete # implementations should not attempt to filter or concatenate the # list in any way. def write(key, negotiations) raise NotImplemented end # Remove all cached entries at the key specified. No error is raised # when the key does not exist. def purge(key) raise NotImplemented end private # Generate a SHA1 hex digest for the specified string. This is a # simple utility method for meta store implementations. def hexdigest(data) Digest::SHA1.hexdigest(data) end public # Concrete MetaStore implementation that uses a simple Hash to store # request/response pairs on the heap. class Heap < MetaStore def initialize(hash={}) @hash = hash end def read(key) if data = @hash[key] Marshal.load(data) else [] end end def write(key, entries) @hash[key] = Marshal.dump(entries) end def purge(key) @hash.delete(key) nil end def to_hash @hash end def self.resolve(uri) new end end HEAP = Heap MEM = HEAP # Concrete MetaStore implementation that stores request/response # pairs on disk. class Disk < MetaStore attr_reader :root def initialize(root="/tmp/rack-cache/meta-#{ARGV[0]}") @root = File.expand_path(root) FileUtils.mkdir_p(root, :mode => 0755) end def read(key) path = key_path(key) File.open(path, 'rb') { |io| Marshal.load(io) } rescue Errno::ENOENT, IOError [] end def write(key, entries) tries = 0 begin path = key_path(key) File.open(path, 'wb') { |io| Marshal.dump(entries, io, -1) } rescue Errno::ENOENT, IOError Dir.mkdir(File.dirname(path), 0755) retry if (tries += 1) == 1 end end def purge(key) path = key_path(key) File.unlink(path) nil rescue Errno::ENOENT, IOError nil end private def key_path(key) File.join(root, spread(hexdigest(key))) end def spread(sha, n=2) sha = sha.dup sha[n,0] = '/' sha end public def self.resolve(uri) path = File.expand_path(uri.opaque || uri.path) new path end end DISK = Disk FILE = Disk # Stores request/response pairs in memcached. Keys are not stored # directly since memcached has a 250-byte limit on key names. Instead, # the SHA1 hexdigest of the key is used. class MemCacheBase < MetaStore extend Rack::Utils # The MemCache object used to communicated with the memcached # daemon. attr_reader :cache # Create MemCache store for the given URI. The URI must specify # a host and may specify a port, namespace, and options: # # memcached://example.com:11211/namespace?opt1=val1&opt2=val2 # # Query parameter names and values are documented with the memcached # library: http://tinyurl.com/4upqnd def self.resolve(uri) if uri.respond_to?(:scheme) server = "#{uri.host}:#{uri.port || '11211'}" options = parse_query(uri.query) options.keys.each do |key| value = case value = options.delete(key) when 'true' ; true when 'false' ; false else value.to_sym end options[key.to_sym] = value end options[:namespace] = uri.path.to_s.sub(/^\//, '') new server, options else # if the object provided is not a URI, pass it straight through # to the underlying implementation. new uri end end end class Dalli < MemCacheBase def initialize(server="localhost:11211", options={}) @cache = if server.respond_to?(:stats) server else require 'dalli' ::Dalli::Client.new(server, options) end end def read(key) key = hexdigest(key) cache.get(key) || [] end def write(key, entries) key = hexdigest(key) cache.set(key, entries) end def purge(key) cache.delete(hexdigest(key)) nil end end class MemCached < MemCacheBase # The Memcached instance used to communicated with the memcached # daemon. attr_reader :cache def initialize(server="localhost:11211", options={}) options[:prefix_key] ||= options.delete(:namespace) if options.key?(:namespace) @cache = if server.respond_to?(:stats) server else require 'memcached' Memcached.new(server, options) end end def read(key) key = hexdigest(key) cache.get(key) rescue Memcached::NotFound [] end def write(key, entries) key = hexdigest(key) cache.set(key, entries) end def purge(key) key = hexdigest(key) cache.delete(key) nil rescue Memcached::NotFound nil end end MEMCACHE = if defined?(::Memcached) MemCached else Dalli end MEMCACHED = MEMCACHE class GAEStore < MetaStore attr_reader :cache def initialize(options = {}) require 'rack/cache/appengine' @cache = Rack::Cache::AppEngine::MemCache.new(options) end def read(key) key = hexdigest(key) cache.get(key) || [] end def write(key, entries) key = hexdigest(key) cache.put(key, entries) end def purge(key) key = hexdigest(key) cache.delete(key) nil end def self.resolve(uri) self.new(:namespace => uri.host) end end GAECACHE = GAEStore GAE = GAEStore end end rack-cache-1.2/lib/rack/cache/key.rb0000644000000000000000000000233211745752772015722 0ustar rootrootrequire 'rack/utils' module Rack::Cache class Key include Rack::Utils # Implement .call, since it seems like the "Rack-y" thing to do. Plus, it # opens the door for cache key generators to just be blocks. def self.call(request) new(request).generate end def initialize(request) @request = request end # Generate a normalized cache key for the request. def generate parts = [] parts << @request.scheme << "://" parts << @request.host if @request.scheme == "https" && @request.port != 443 || @request.scheme == "http" && @request.port != 80 parts << ":" << @request.port.to_s end parts << @request.script_name parts << @request.path_info if qs = query_string parts << "?" parts << qs end parts.join end private # Build a normalized query string by alphabetizing all keys/values # and applying consistent escaping. def query_string return nil if @request.query_string.nil? @request.query_string.split(/[&;] */n). map { |p| unescape(p).split('=', 2) }. sort. map { |k,v| "#{escape(k)}=#{escape(v)}" }. join('&') end end end rack-cache-1.2/lib/rack/cache/context.rb0000644000000000000000000002141011745752772016614 0ustar rootrootrequire 'rack/cache/options' require 'rack/cache/request' require 'rack/cache/response' require 'rack/cache/storage' module Rack::Cache # Implements Rack's middleware interface and provides the context for all # cache logic, including the core logic engine. class Context include Rack::Cache::Options # Array of trace Symbols attr_reader :trace # The Rack application object immediately downstream. attr_reader :backend def initialize(backend, options={}) @backend = backend @trace = [] @env = nil initialize_options options yield self if block_given? @private_header_keys = private_headers.map { |name| "HTTP_#{name.upcase.tr('-', '_')}" } end # The configured MetaStore instance. Changing the rack-cache.metastore # value effects the result of this method immediately. def metastore uri = options['rack-cache.metastore'] storage.resolve_metastore_uri(uri) end # The configured EntityStore instance. Changing the rack-cache.entitystore # value effects the result of this method immediately. def entitystore uri = options['rack-cache.entitystore'] storage.resolve_entitystore_uri(uri) end # The Rack call interface. The receiver acts as a prototype and runs # each request in a dup object unless the +rack.run_once+ variable is # set in the environment. def call(env) if env['rack.run_once'] call! env else clone.call! env end end # The real Rack call interface. The caching logic is performed within # the context of the receiver. def call!(env) @trace = [] @default_options.each { |k,v| env[k] ||= v } @env = env @request = Request.new(@env.dup.freeze) response = if @request.get? || @request.head? if !@env['HTTP_EXPECT'] && !@env['rack-cache.force-pass'] lookup else pass end else invalidate end # log trace and set X-Rack-Cache tracing header trace = @trace.join(', ') response.headers['X-Rack-Cache'] = trace # write log message to rack.errors if verbose? message = "cache: [%s %s] %s\n" % [@request.request_method, @request.fullpath, trace] @env['rack.errors'].write(message) end # tidy up response a bit if (@request.get? || @request.head?) && not_modified?(response) response.not_modified! end if @request.head? response.body.close if response.body.respond_to?(:close) response.body = [] end response.to_a end private # Record that an event took place. def record(event) @trace << event end # Does the request include authorization or other sensitive information # that should cause the response to be considered private by default? # Private responses are not stored in the cache. def private_request? @private_header_keys.any? { |key| @env.key?(key) } end # Determine if the #response validators (ETag, Last-Modified) matches # a conditional value specified in #request. def not_modified?(response) last_modified = @request.env['HTTP_IF_MODIFIED_SINCE'] if etags = @request.env['HTTP_IF_NONE_MATCH'] etags = etags.split(/\s*,\s*/) (etags.include?(response.etag) || etags.include?('*')) && (!last_modified || response.last_modified == last_modified) elsif last_modified response.last_modified == last_modified end end # Whether the cache entry is "fresh enough" to satisfy the request. def fresh_enough?(entry) if entry.fresh? if allow_revalidate? && max_age = @request.cache_control.max_age max_age > 0 && max_age >= entry.age else true end end end # Delegate the request to the backend and create the response. def forward Response.new(*backend.call(@env)) end # The request is sent to the backend, and the backend's response is sent # to the client, but is not entered into the cache. def pass record :pass forward end # Invalidate POST, PUT, DELETE and all methods not understood by this cache # See RFC2616 13.10 def invalidate metastore.invalidate(@request, entitystore) rescue Exception => e log_error(e) pass else record :invalidate pass end # Try to serve the response from cache. When a matching cache entry is # found and is fresh, use it as the response without forwarding any # request to the backend. When a matching cache entry is found but is # stale, attempt to #validate the entry with the backend using conditional # GET. When no matching cache entry is found, trigger #miss processing. def lookup if @request.no_cache? && allow_reload? record :reload fetch else begin entry = metastore.lookup(@request, entitystore) rescue Exception => e log_error(e) return pass end if entry if fresh_enough?(entry) record :fresh entry.headers['Age'] = entry.age.to_s entry else record :stale validate(entry) end else record :miss fetch end end end # Validate that the cache entry is fresh. The original request is used # as a template for a conditional GET request with the backend. def validate(entry) # send no head requests because we want content @env['REQUEST_METHOD'] = 'GET' # add our cached last-modified validator to the environment @env['HTTP_IF_MODIFIED_SINCE'] = entry.last_modified # Add our cached etag validator to the environment. # We keep the etags from the client to handle the case when the client # has a different private valid entry which is not cached here. cached_etags = entry.etag.to_s.split(/\s*,\s*/) request_etags = @request.env['HTTP_IF_NONE_MATCH'].to_s.split(/\s*,\s*/) etags = (cached_etags + request_etags).uniq @env['HTTP_IF_NONE_MATCH'] = etags.empty? ? nil : etags.join(', ') response = forward if response.status == 304 record :valid # Check if the response validated which is not cached here etag = response.headers['ETag'] return response if etag && request_etags.include?(etag) && !cached_etags.include?(etag) entry = entry.dup entry.headers.delete('Date') %w[Date Expires Cache-Control ETag Last-Modified].each do |name| next unless value = response.headers[name] entry.headers[name] = value end # even though it's empty, be sure to close the response body from upstream # because middleware use close to signal end of response response.body.close if response.body.respond_to?(:close) response = entry else record :invalid end store(response) if response.cacheable? response end # The cache missed or a reload is required. Forward the request to the # backend and determine whether the response should be stored. This allows # conditional / validation requests through to the backend but performs no # caching of the response when the backend returns a 304. def fetch # send no head requests because we want content @env['REQUEST_METHOD'] = 'GET' response = forward # Mark the response as explicitly private if any of the private # request headers are present and the response was not explicitly # declared public. if private_request? && !response.cache_control.public? response.private = true elsif default_ttl > 0 && response.ttl.nil? && !response.cache_control.must_revalidate? # assign a default TTL for the cache entry if none was specified in # the response; the must-revalidate cache control directive disables # default ttl assigment. response.ttl = default_ttl end store(response) if response.cacheable? response end # Write the response to the cache. def store(response) strip_ignore_headers(response) metastore.store(@request, response, entitystore) response.headers['Age'] = response.age.to_s rescue Exception => e log_error(e) nil else record :store end # Remove all ignored response headers before writing to the cache. def strip_ignore_headers(response) stripped_values = ignore_headers.map { |name| response.headers.delete(name) } record :ignore if stripped_values.any? end def log_error(exception) @env['rack.errors'].write("cache error: #{exception.message}\n#{exception.backtrace.join("\n")}\n") end end end rack-cache-1.2/lib/rack/cache/response.rb0000644000000000000000000002036011745752772016771 0ustar rootrootrequire 'time' require 'set' require 'rack/response' require 'rack/utils' require 'rack/cache/cachecontrol' module Rack::Cache # Provides access to the response generated by the downstream application. The # +response+, +original_response+, and +entry+ objects exposed by the Core # caching engine are instances of this class. # # Response objects respond to a variety of convenience methods, including # those defined in Rack::Response::Helpers, Rack::Cache::Headers, # and Rack::Cache::ResponseHeaders. # # Note that Rack::Cache::Response is not a subclass of Rack::Response and does # not perform many of the same initialization and finalization tasks. For # example, the body is not slurped during initialization and there are no # facilities for generating response output. class Response include Rack::Response::Helpers # Rack response tuple accessors. attr_accessor :status, :headers, :body # The time when the Response object was instantiated. attr_reader :now # Create a Response instance given the response status code, header hash, # and body. def initialize(status, headers, body) @status = status.to_i @headers = Rack::Utils::HeaderHash.new(headers) @body = body @now = Time.now @headers['Date'] ||= @now.httpdate end def initialize_copy(other) super @headers = other.headers.dup end # Return the status, headers, and body in a three-tuple. def to_a [status, headers.to_hash, body] end # Status codes of responses that MAY be stored by a cache or used in reply # to a subsequent request. # # http://tools.ietf.org/html/rfc2616#section-13.4 CACHEABLE_RESPONSE_CODES = [ 200, # OK 203, # Non-Authoritative Information 300, # Multiple Choices 301, # Moved Permanently 302, # Found 404, # Not Found 410 # Gone ].to_set # A Hash of name=value pairs that correspond to the Cache-Control header. # Valueless parameters (e.g., must-revalidate, no-store) have a Hash value # of true. This method always returns a Hash, empty if no Cache-Control # header is present. def cache_control @cache_control ||= CacheControl.new(headers['Cache-Control']) end # Set the Cache-Control header to the values specified by the Hash. See # the #cache_control method for information on expected Hash structure. def cache_control=(value) if value.respond_to? :to_hash cache_control.clear cache_control.merge!(value) value = cache_control.to_s end if value.nil? || value.empty? headers.delete('Cache-Control') else headers['Cache-Control'] = value end end # Determine if the response is "fresh". Fresh responses may be served from # cache without any interaction with the origin. A response is considered # fresh when it includes a Cache-Control/max-age indicator or Expiration # header and the calculated age is less than the freshness lifetime. def fresh? ttl && ttl > 0 end # Determine if the response is worth caching under any circumstance. Responses # marked "private" with an explicit Cache-Control directive are considered # uncacheable # # Responses with neither a freshness lifetime (Expires, max-age) nor cache # validator (Last-Modified, ETag) are considered uncacheable. def cacheable? return false unless CACHEABLE_RESPONSE_CODES.include?(status) return false if cache_control.no_store? || cache_control.private? validateable? || fresh? end # Determine if the response includes headers that can be used to validate # the response with the origin using a conditional GET request. def validateable? headers.key?('Last-Modified') || headers.key?('ETag') end # Mark the response "private", making it ineligible for serving other # clients. def private=(value) value = value ? true : nil self.cache_control = cache_control. merge('public' => !value, 'private' => value) end # Indicates that the cache must not serve a stale response in any # circumstance without first revalidating with the origin. When present, # the TTL of the response should not be overriden to be greater than the # value provided by the origin. def must_revalidate? cache_control.must_revalidate || cache_control.proxy_revalidate end # Mark the response stale by setting the Age header to be equal to the # maximum age of the response. def expire! headers['Age'] = max_age.to_s if fresh? end # The date, as specified by the Date header. When no Date header is present, # set the Date header to Time.now and return. def date if date = headers['Date'] Time.httpdate(date) else headers['Date'] = now.httpdate unless headers.frozen? now end end # The age of the response. def age (headers['Age'] || [(now - date).to_i, 0].max).to_i end # The number of seconds after the time specified in the response's Date # header when the the response should no longer be considered fresh. First # check for a s-maxage directive, then a max-age directive, and then fall # back on an expires header; return nil when no maximum age can be # established. def max_age cache_control.shared_max_age || cache_control.max_age || (expires && (expires - date)) end # The value of the Expires header as a Time object. def expires headers['Expires'] && Time.httpdate(headers['Expires']) end # The number of seconds after which the response should no longer # be considered fresh. Sets the Cache-Control max-age directive. def max_age=(value) self.cache_control = cache_control.merge('max-age' => value.to_s) end # Like #max_age= but sets the s-maxage directive, which applies only # to shared caches. def shared_max_age=(value) self.cache_control = cache_control.merge('s-maxage' => value.to_s) end # The response's time-to-live in seconds, or nil when no freshness # information is present in the response. When the responses #ttl # is <= 0, the response may not be served from cache without first # revalidating with the origin. def ttl max_age - age if max_age end # Set the response's time-to-live for shared caches to the specified number # of seconds. This adjusts the Cache-Control/s-maxage directive. def ttl=(seconds) self.shared_max_age = age + seconds end # Set the response's time-to-live for private/client caches. This adjusts # the Cache-Control/max-age directive. def client_ttl=(seconds) self.max_age = age + seconds end # The String value of the Last-Modified header exactly as it appears # in the response (i.e., no date parsing / conversion is performed). def last_modified headers['Last-Modified'] end # The literal value of ETag HTTP header or nil if no ETag is specified. def etag headers['ETag'] end # Headers that MUST NOT be included with 304 Not Modified responses. # # http://tools.ietf.org/html/rfc2616#section-10.3.5 NOT_MODIFIED_OMIT_HEADERS = %w[ Allow Content-Encoding Content-Language Content-Length Content-MD5 Content-Type Last-Modified ].to_set # Modify the response so that it conforms to the rules defined for # '304 Not Modified'. This sets the status, removes the body, and # discards any headers that MUST NOT be included in 304 responses. # # http://tools.ietf.org/html/rfc2616#section-10.3.5 def not_modified! body.close if body.respond_to?(:close) self.status = 304 self.body = [] NOT_MODIFIED_OMIT_HEADERS.each { |name| headers.delete(name) } nil end # The literal value of the Vary header, or nil when no header is present. def vary headers['Vary'] end # Does the response include a Vary header? def vary? ! vary.nil? end # An array of header names given in the Vary header or an empty # array when no Vary header is present. def vary_header_names return [] unless vary = headers['Vary'] vary.split(/[\s,]+/) end end end rack-cache-1.2/lib/rack/cache/cachecontrol.rb0000644000000000000000000002244711745752772017607 0ustar rootrootmodule Rack module Cache # Parses a Cache-Control header and exposes the directives as a Hash. # Directives that do not have values are set to +true+. class CacheControl < Hash def initialize(value=nil) parse(value) end # Indicates that the response MAY be cached by any cache, even if it # would normally be non-cacheable or cacheable only within a non- # shared cache. # # A response may be considered public without this directive if the # private directive is not set and the request does not include an # Authorization header. def public? self['public'] end # Indicates that all or part of the response message is intended for # a single user and MUST NOT be cached by a shared cache. This # allows an origin server to state that the specified parts of the # response are intended for only one user and are not a valid # response for requests by other users. A private (non-shared) cache # MAY cache the response. # # Note: This usage of the word private only controls where the # response may be cached, and cannot ensure the privacy of the # message content. def private? self['private'] end # When set in a response, a cache MUST NOT use the response to satisfy a # subsequent request without successful revalidation with the origin # server. This allows an origin server to prevent caching even by caches # that have been configured to return stale responses to client requests. # # Note that this does not necessary imply that the response may not be # stored by the cache, only that the cache cannot serve it without first # making a conditional GET request with the origin server. # # When set in a request, the server MUST NOT use a cached copy for its # response. This has quite different semantics compared to the no-cache # directive on responses. When the client specifies no-cache, it causes # an end-to-end reload, forcing each cache to update their cached copies. def no_cache? self['no-cache'] end # Indicates that the response MUST NOT be stored under any circumstances. # # The purpose of the no-store directive is to prevent the # inadvertent release or retention of sensitive information (for # example, on backup tapes). The no-store directive applies to the # entire message, and MAY be sent either in a response or in a # request. If sent in a request, a cache MUST NOT store any part of # either this request or any response to it. If sent in a response, # a cache MUST NOT store any part of either this response or the # request that elicited it. This directive applies to both non- # shared and shared caches. "MUST NOT store" in this context means # that the cache MUST NOT intentionally store the information in # non-volatile storage, and MUST make a best-effort attempt to # remove the information from volatile storage as promptly as # possible after forwarding it. # # The purpose of this directive is to meet the stated requirements # of certain users and service authors who are concerned about # accidental releases of information via unanticipated accesses to # cache data structures. While the use of this directive might # improve privacy in some cases, we caution that it is NOT in any # way a reliable or sufficient mechanism for ensuring privacy. In # particular, malicious or compromised caches might not recognize or # obey this directive, and communications networks might be # vulnerable to eavesdropping. def no_store? self['no-store'] end # The expiration time of an entity MAY be specified by the origin # server using the Expires header (see section 14.21). Alternatively, # it MAY be specified using the max-age directive in a response. When # the max-age cache-control directive is present in a cached response, # the response is stale if its current age is greater than the age # value given (in seconds) at the time of a new request for that # resource. The max-age directive on a response implies that the # response is cacheable (i.e., "public") unless some other, more # restrictive cache directive is also present. # # If a response includes both an Expires header and a max-age # directive, the max-age directive overrides the Expires header, even # if the Expires header is more restrictive. This rule allows an origin # server to provide, for a given response, a longer expiration time to # an HTTP/1.1 (or later) cache than to an HTTP/1.0 cache. This might be # useful if certain HTTP/1.0 caches improperly calculate ages or # expiration times, perhaps due to desynchronized clocks. # # Many HTTP/1.0 cache implementations will treat an Expires value that # is less than or equal to the response Date value as being equivalent # to the Cache-Control response directive "no-cache". If an HTTP/1.1 # cache receives such a response, and the response does not include a # Cache-Control header field, it SHOULD consider the response to be # non-cacheable in order to retain compatibility with HTTP/1.0 servers. # # When the max-age directive is included in the request, it indicates # that the client is willing to accept a response whose age is no # greater than the specified time in seconds. def max_age self['max-age'].to_i if key?('max-age') end # If a response includes an s-maxage directive, then for a shared # cache (but not for a private cache), the maximum age specified by # this directive overrides the maximum age specified by either the # max-age directive or the Expires header. The s-maxage directive # also implies the semantics of the proxy-revalidate directive. i.e., # that the shared cache must not use the entry after it becomes stale # to respond to a subsequent request without first revalidating it with # the origin server. The s-maxage directive is always ignored by a # private cache. def shared_max_age self['s-maxage'].to_i if key?('s-maxage') end alias_method :s_maxage, :shared_max_age # Because a cache MAY be configured to ignore a server's specified # expiration time, and because a client request MAY include a max- # stale directive (which has a similar effect), the protocol also # includes a mechanism for the origin server to require revalidation # of a cache entry on any subsequent use. When the must-revalidate # directive is present in a response received by a cache, that cache # MUST NOT use the entry after it becomes stale to respond to a # subsequent request without first revalidating it with the origin # server. (I.e., the cache MUST do an end-to-end revalidation every # time, if, based solely on the origin server's Expires or max-age # value, the cached response is stale.) # # The must-revalidate directive is necessary to support reliable # operation for certain protocol features. In all circumstances an # HTTP/1.1 cache MUST obey the must-revalidate directive; in # particular, if the cache cannot reach the origin server for any # reason, it MUST generate a 504 (Gateway Timeout) response. # # Servers SHOULD send the must-revalidate directive if and only if # failure to revalidate a request on the entity could result in # incorrect operation, such as a silently unexecuted financial # transaction. Recipients MUST NOT take any automated action that # violates this directive, and MUST NOT automatically provide an # unvalidated copy of the entity if revalidation fails. def must_revalidate? self['must-revalidate'] end # The proxy-revalidate directive has the same meaning as the must- # revalidate directive, except that it does not apply to non-shared # user agent caches. It can be used on a response to an # authenticated request to permit the user's cache to store and # later return the response without needing to revalidate it (since # it has already been authenticated once by that user), while still # requiring proxies that service many users to revalidate each time # (in order to make sure that each user has been authenticated). # Note that such authenticated responses also need the public cache # control directive in order to allow them to be cached at all. def proxy_revalidate? self['proxy-revalidate'] end def to_s bools, vals = [], [] each do |key,value| if value == true bools << key elsif value vals << "#{key}=#{value}" end end (bools.sort + vals.sort).join(', ') end private def parse(value) return if value.nil? || value.empty? value.delete(' ').split(',').each do |part| next if part.empty? name, value = part.split('=', 2) self[name.downcase] = (value || true) unless name.empty? end self end end end end rack-cache-1.2/lib/rack/cache/options.rb0000644000000000000000000001377711745752772016644 0ustar rootrootrequire 'rack/cache/key' require 'rack/cache/storage' module Rack::Cache # Configuration options and utility methods for option access. Rack::Cache # uses the Rack Environment to store option values. All options documented # below are stored in the Rack Environment as "rack-cache.