PNG  IHDRxsBIT|d pHYs+tEXtSoftwarewww.inkscape.org<,tEXtComment File Manager

File Manager

Path: /opt/golang/1.22.0/src/html/template/

Viewing File: js_test.go

// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package template

import (
	"math"
	"strings"
	"testing"
)

func TestNextJsCtx(t *testing.T) {
	tests := []struct {
		jsCtx jsCtx
		s     string
	}{
		// Statement terminators precede regexps.
		{jsCtxRegexp, ";"},
		// This is not airtight.
		//     ({ valueOf: function () { return 1 } } / 2)
		// is valid JavaScript but in practice, devs do not do this.
		// A block followed by a statement starting with a RegExp is
		// much more common:
		//     while (x) {...} /foo/.test(x) || panic()
		{jsCtxRegexp, "}"},
		// But member, call, grouping, and array expression terminators
		// precede div ops.
		{jsCtxDivOp, ")"},
		{jsCtxDivOp, "]"},
		// At the start of a primary expression, array, or expression
		// statement, expect a regexp.
		{jsCtxRegexp, "("},
		{jsCtxRegexp, "["},
		{jsCtxRegexp, "{"},
		// Assignment operators precede regexps as do all exclusively
		// prefix and binary operators.
		{jsCtxRegexp, "="},
		{jsCtxRegexp, "+="},
		{jsCtxRegexp, "*="},
		{jsCtxRegexp, "*"},
		{jsCtxRegexp, "!"},
		// Whether the + or - is infix or prefix, it cannot precede a
		// div op.
		{jsCtxRegexp, "+"},
		{jsCtxRegexp, "-"},
		// An incr/decr op precedes a div operator.
		// This is not airtight. In (g = ++/h/i) a regexp follows a
		// pre-increment operator, but in practice devs do not try to
		// increment or decrement regular expressions.
		// (g++/h/i) where ++ is a postfix operator on g is much more
		// common.
		{jsCtxDivOp, "--"},
		{jsCtxDivOp, "++"},
		{jsCtxDivOp, "x--"},
		// When we have many dashes or pluses, then they are grouped
		// left to right.
		{jsCtxRegexp, "x---"}, // A postfix -- then a -.
		// return followed by a slash returns the regexp literal or the
		// slash starts a regexp literal in an expression statement that
		// is dead code.
		{jsCtxRegexp, "return"},
		{jsCtxRegexp, "return "},
		{jsCtxRegexp, "return\t"},
		{jsCtxRegexp, "return\n"},
		{jsCtxRegexp, "return\u2028"},
		// Identifiers can be divided and cannot validly be preceded by
		// a regular expressions. Semicolon insertion cannot happen
		// between an identifier and a regular expression on a new line
		// because the one token lookahead for semicolon insertion has
		// to conclude that it could be a div binary op and treat it as
		// such.
		{jsCtxDivOp, "x"},
		{jsCtxDivOp, "x "},
		{jsCtxDivOp, "x\t"},
		{jsCtxDivOp, "x\n"},
		{jsCtxDivOp, "x\u2028"},
		{jsCtxDivOp, "preturn"},
		// Numbers precede div ops.
		{jsCtxDivOp, "0"},
		// Dots that are part of a number are div preceders.
		{jsCtxDivOp, "0."},
		// Some JS interpreters treat NBSP as a normal space, so
		// we must too in order to properly escape things.
		{jsCtxRegexp, "=\u00A0"},
	}

	for _, test := range tests {
		if ctx := nextJSCtx([]byte(test.s), jsCtxRegexp); ctx != test.jsCtx {
			t.Errorf("%q: want %s got %s", test.s, test.jsCtx, ctx)
		}
		if ctx := nextJSCtx([]byte(test.s), jsCtxDivOp); ctx != test.jsCtx {
			t.Errorf("%q: want %s got %s", test.s, test.jsCtx, ctx)
		}
	}

	if nextJSCtx([]byte("   "), jsCtxRegexp) != jsCtxRegexp {
		t.Error("Blank tokens")
	}

	if nextJSCtx([]byte("   "), jsCtxDivOp) != jsCtxDivOp {
		t.Error("Blank tokens")
	}
}

func TestJSValEscaper(t *testing.T) {
	tests := []struct {
		x  any
		js string
	}{
		{int(42), " 42 "},
		{uint(42), " 42 "},
		{int16(42), " 42 "},
		{uint16(42), " 42 "},
		{int32(-42), " -42 "},
		{uint32(42), " 42 "},
		{int16(-42), " -42 "},
		{uint16(42), " 42 "},
		{int64(-42), " -42 "},
		{uint64(42), " 42 "},
		{uint64(1) << 53, " 9007199254740992 "},
		// ulp(1 << 53) > 1 so this loses precision in JS
		// but it is still a representable integer literal.
		{uint64(1)<<53 + 1, " 9007199254740993 "},
		{float32(1.0), " 1 "},
		{float32(-1.0), " -1 "},
		{float32(0.5), " 0.5 "},
		{float32(-0.5), " -0.5 "},
		{float32(1.0) / float32(256), " 0.00390625 "},
		{float32(0), " 0 "},
		{math.Copysign(0, -1), " -0 "},
		{float64(1.0), " 1 "},
		{float64(-1.0), " -1 "},
		{float64(0.5), " 0.5 "},
		{float64(-0.5), " -0.5 "},
		{float64(0), " 0 "},
		{math.Copysign(0, -1), " -0 "},
		{"", `""`},
		{"foo", `"foo"`},
		// Newlines.
		{"\r\n\u2028\u2029", `"\r\n\u2028\u2029"`},
		// "\v" == "v" on IE 6 so use "\u000b" instead.
		{"\t\x0b", `"\t\u000b"`},
		{struct{ X, Y int }{1, 2}, `{"X":1,"Y":2}`},
		{[]any{}, "[]"},
		{[]any{42, "foo", nil}, `[42,"foo",null]`},
		{[]string{"<!--", "</script>", "-->"}, `["\u003c!--","\u003c/script\u003e","--\u003e"]`},
		{"<!--", `"\u003c!--"`},
		{"-->", `"--\u003e"`},
		{"<![CDATA[", `"\u003c![CDATA["`},
		{"]]>", `"]]\u003e"`},
		{"</script", `"\u003c/script"`},
		{"\U0001D11E", "\"\U0001D11E\""}, // or "\uD834\uDD1E"
		{nil, " null "},
	}

	for _, test := range tests {
		if js := jsValEscaper(test.x); js != test.js {
			t.Errorf("%+v: want\n\t%q\ngot\n\t%q", test.x, test.js, js)
		}
		// Make sure that escaping corner cases are not broken
		// by nesting.
		a := []any{test.x}
		want := "[" + strings.TrimSpace(test.js) + "]"
		if js := jsValEscaper(a); js != want {
			t.Errorf("%+v: want\n\t%q\ngot\n\t%q", a, want, js)
		}
	}
}

func TestJSStrEscaper(t *testing.T) {
	tests := []struct {
		x   any
		esc string
	}{
		{"", ``},
		{"foo", `foo`},
		{"\u0000", `\u0000`},
		{"\t", `\t`},
		{"\n", `\n`},
		{"\r", `\r`},
		{"\u2028", `\u2028`},
		{"\u2029", `\u2029`},
		{"\\", `\\`},
		{"\\n", `\\n`},
		{"foo\r\nbar", `foo\r\nbar`},
		// Preserve attribute boundaries.
		{`"`, `\u0022`},
		{`'`, `\u0027`},
		// Allow embedding in HTML without further escaping.
		{`&amp;`, `\u0026amp;`},
		// Prevent breaking out of text node and element boundaries.
		{"</script>", `\u003c\/script\u003e`},
		{"<![CDATA[", `\u003c![CDATA[`},
		{"]]>", `]]\u003e`},
		// https://dev.w3.org/html5/markup/aria/syntax.html#escaping-text-span
		//   "The text in style, script, title, and textarea elements
		//   must not have an escaping text span start that is not
		//   followed by an escaping text span end."
		// Furthermore, spoofing an escaping text span end could lead
		// to different interpretation of a </script> sequence otherwise
		// masked by the escaping text span, and spoofing a start could
		// allow regular text content to be interpreted as script
		// allowing script execution via a combination of a JS string
		// injection followed by an HTML text injection.
		{"<!--", `\u003c!--`},
		{"-->", `--\u003e`},
		// From https://code.google.com/p/doctype/wiki/ArticleUtf7
		{"+ADw-script+AD4-alert(1)+ADw-/script+AD4-",
			`\u002bADw-script\u002bAD4-alert(1)\u002bADw-\/script\u002bAD4-`,
		},
		// Invalid UTF-8 sequence
		{"foo\xA0bar", "foo\xA0bar"},
		// Invalid unicode scalar value.
		{"foo\xed\xa0\x80bar", "foo\xed\xa0\x80bar"},
	}

	for _, test := range tests {
		esc := jsStrEscaper(test.x)
		if esc != test.esc {
			t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
		}
	}
}

func TestJSRegexpEscaper(t *testing.T) {
	tests := []struct {
		x   any
		esc string
	}{
		{"", `(?:)`},
		{"foo", `foo`},
		{"\u0000", `\u0000`},
		{"\t", `\t`},
		{"\n", `\n`},
		{"\r", `\r`},
		{"\u2028", `\u2028`},
		{"\u2029", `\u2029`},
		{"\\", `\\`},
		{"\\n", `\\n`},
		{"foo\r\nbar", `foo\r\nbar`},
		// Preserve attribute boundaries.
		{`"`, `\u0022`},
		{`'`, `\u0027`},
		// Allow embedding in HTML without further escaping.
		{`&amp;`, `\u0026amp;`},
		// Prevent breaking out of text node and element boundaries.
		{"</script>", `\u003c\/script\u003e`},
		{"<![CDATA[", `\u003c!\[CDATA\[`},
		{"]]>", `\]\]\u003e`},
		// Escaping text spans.
		{"<!--", `\u003c!\-\-`},
		{"-->", `\-\-\u003e`},
		{"*", `\*`},
		{"+", `\u002b`},
		{"?", `\?`},
		{"[](){}", `\[\]\(\)\{\}`},
		{"$foo|x.y", `\$foo\|x\.y`},
		{"x^y", `x\^y`},
	}

	for _, test := range tests {
		esc := jsRegexpEscaper(test.x)
		if esc != test.esc {
			t.Errorf("%q: want %q got %q", test.x, test.esc, esc)
		}
	}
}

func TestEscapersOnLower7AndSelectHighCodepoints(t *testing.T) {
	input := ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f" +
		"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
		` !"#$%&'()*+,-./` +
		`0123456789:;<=>?` +
		`@ABCDEFGHIJKLMNO` +
		`PQRSTUVWXYZ[\]^_` +
		"`abcdefghijklmno" +
		"pqrstuvwxyz{|}~\x7f" +
		"\u00A0\u0100\u2028\u2029\ufeff\U0001D11E")

	tests := []struct {
		name    string
		escaper func(...any) string
		escaped string
	}{
		{
			"jsStrEscaper",
			jsStrEscaper,
			`\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` +
				`\u0008\t\n\u000b\f\r\u000e\u000f` +
				`\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` +
				`\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
				` !\u0022#$%\u0026\u0027()*\u002b,-.\/` +
				`0123456789:;\u003c=\u003e?` +
				`@ABCDEFGHIJKLMNO` +
				`PQRSTUVWXYZ[\\]^_` +
				"\\u0060abcdefghijklmno" +
				"pqrstuvwxyz{|}~\u007f" +
				"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
		},
		{
			"jsRegexpEscaper",
			jsRegexpEscaper,
			`\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007` +
				`\u0008\t\n\u000b\f\r\u000e\u000f` +
				`\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017` +
				`\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f` +
				` !\u0022#\$%\u0026\u0027\(\)\*\u002b,\-\.\/` +
				`0123456789:;\u003c=\u003e\?` +
				`@ABCDEFGHIJKLMNO` +
				`PQRSTUVWXYZ\[\\\]\^_` +
				"`abcdefghijklmno" +
				`pqrstuvwxyz\{\|\}~` + "\u007f" +
				"\u00A0\u0100\\u2028\\u2029\ufeff\U0001D11E",
		},
	}

	for _, test := range tests {
		if s := test.escaper(input); s != test.escaped {
			t.Errorf("%s once: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
			continue
		}

		// Escape it rune by rune to make sure that any
		// fast-path checking does not break escaping.
		var buf strings.Builder
		for _, c := range input {
			buf.WriteString(test.escaper(string(c)))
		}

		if s := buf.String(); s != test.escaped {
			t.Errorf("%s rune-wise: want\n\t%q\ngot\n\t%q", test.name, test.escaped, s)
			continue
		}
	}
}

func TestIsJsMimeType(t *testing.T) {
	tests := []struct {
		in  string
		out bool
	}{
		{"application/javascript;version=1.8", true},
		{"application/javascript;version=1.8;foo=bar", true},
		{"application/javascript/version=1.8", false},
		{"text/javascript", true},
		{"application/json", true},
		{"application/ld+json", true},
		{"module", true},
	}

	for _, test := range tests {
		if isJSType(test.in) != test.out {
			t.Errorf("isJSType(%q) = %v, want %v", test.in, !test.out, test.out)
		}
	}
}

func BenchmarkJSValEscaperWithNum(b *testing.B) {
	for i := 0; i < b.N; i++ {
		jsValEscaper(3.141592654)
	}
}

func BenchmarkJSValEscaperWithStr(b *testing.B) {
	for i := 0; i < b.N; i++ {
		jsValEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
	}
}

func BenchmarkJSValEscaperWithStrNoSpecials(b *testing.B) {
	for i := 0; i < b.N; i++ {
		jsValEscaper("The quick, brown fox jumps over the lazy dog")
	}
}

func BenchmarkJSValEscaperWithObj(b *testing.B) {
	o := struct {
		S string
		N int
	}{
		"The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>\u2028",
		42,
	}
	for i := 0; i < b.N; i++ {
		jsValEscaper(o)
	}
}

func BenchmarkJSValEscaperWithObjNoSpecials(b *testing.B) {
	o := struct {
		S string
		N int
	}{
		"The quick, brown fox jumps over the lazy dog",
		42,
	}
	for i := 0; i < b.N; i++ {
		jsValEscaper(o)
	}
}

func BenchmarkJSStrEscaperNoSpecials(b *testing.B) {
	for i := 0; i < b.N; i++ {
		jsStrEscaper("The quick, brown fox jumps over the lazy dog.")
	}
}

func BenchmarkJSStrEscaper(b *testing.B) {
	for i := 0; i < b.N; i++ {
		jsStrEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
	}
}

func BenchmarkJSRegexpEscaperNoSpecials(b *testing.B) {
	for i := 0; i < b.N; i++ {
		jsRegexpEscaper("The quick, brown fox jumps over the lazy dog")
	}
}

func BenchmarkJSRegexpEscaper(b *testing.B) {
	for i := 0; i < b.N; i++ {
		jsRegexpEscaper("The <i>quick</i>,\r\n<span style='color:brown'>brown</span> fox jumps\u2028over the <canine class=\"lazy\">dog</canine>")
	}
}
b IDATxytVսϓ22 A@IR :hCiZ[v*E:WũZA ^dQeQ @ !jZ'>gsV仿$|?g)&x-EIENT ;@xT.i%-X}SvS5.r/UHz^_$-W"w)Ɗ/@Z &IoX P$K}JzX:;` &, ŋui,e6mX ԵrKb1ԗ)DADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADA݀!I*]R;I2$eZ#ORZSrr6mteffu*((Pu'v{DIߔ4^pIm'77WEEE;vƎ4-$]'RI{\I&G :IHJ DWBB=\WR޽m o$K(V9ABB.}jѢv`^?IOȅ} ڶmG}T#FJ`56$-ھ}FI&v;0(h;Б38CӧOWf!;A i:F_m9s&|q%=#wZprrrla A &P\\СC[A#! {olF} `E2}MK/vV)i{4BffV\|ۭX`b@kɶ@%i$K z5zhmX[IXZ` 'b%$r5M4º/l ԃߖxhʔ)[@=} K6IM}^5k㏷݆z ΗÿO:gdGBmyT/@+Vɶ纽z񕏵l.y޴it뭷zV0[Y^>Wsqs}\/@$(T7f.InݺiR$푔n.~?H))\ZRW'Mo~v Ov6oԃxz! S,&xm/yɞԟ?'uaSѽb,8GלKboi&3t7Y,)JJ c[nzӳdE&KsZLӄ I?@&%ӟ۶mSMMњ0iؐSZ,|J+N ~,0A0!5%Q-YQQa3}$_vVrf9f?S8`zDADADADADADADADADAdqP,تmMmg1V?rSI꒟]u|l RCyEf٢9 jURbztѰ!m5~tGj2DhG*{H9)꒟ר3:(+3\?/;TUݭʴ~S6lڧUJ*i$d(#=Yݺd{,p|3B))q:vN0Y.jkק6;SɶVzHJJЀ-utѹսk>QUU\޲~]fFnK?&ߡ5b=z9)^|u_k-[y%ZNU6 7Mi:]ۦtk[n X(e6Bb."8cۭ|~teuuw|ήI-5"~Uk;ZicEmN/:]M> cQ^uiƞ??Ңpc#TUU3UakNwA`:Y_V-8.KKfRitv޲* 9S6ֿj,ՃNOMߤ]z^fOh|<>@Å5 _/Iu?{SY4hK/2]4%it5q]GGe2%iR| W&f*^]??vq[LgE_3f}Fxu~}qd-ږFxu~I N>\;͗O֊:̗WJ@BhW=y|GgwܷH_NY?)Tdi'?խwhlmQi !SUUsw4kӺe4rfxu-[nHtMFj}H_u~w>)oV}(T'ebʒv3_[+vn@Ȭ\S}ot}w=kHFnxg S 0eޢm~l}uqZfFoZuuEg `zt~? b;t%>WTkķh[2eG8LIWx,^\thrl^Ϊ{=dž<}qV@ ⠨Wy^LF_>0UkDuʫuCs$)Iv:IK;6ֲ4{^6եm+l3>݆uM 9u?>Zc }g~qhKwڭeFMM~pМuqǿz6Tb@8@Y|jx](^]gf}M"tG -w.@vOqh~/HII`S[l.6nØXL9vUcOoB\xoǤ'T&IǍQw_wpv[kmO{w~>#=P1Pɞa-we:iǏlHo׈꒟f9SzH?+shk%Fs:qVhqY`jvO'ρ?PyX3lх]˾uV{ݞ]1,MzYNW~̈́ joYn}ȚF߾׮mS]F z+EDxm/d{F{-W-4wY듏:??_gPf ^3ecg ҵs8R2מz@TANGj)}CNi/R~}c:5{!ZHӋӾ6}T]G]7W6^n 9*,YqOZj:P?Q DFL|?-^.Ɵ7}fFh׶xe2Pscz1&5\cn[=Vn[ĶE鎀uˌd3GII k;lNmشOuuRVfBE]ۣeӶu :X-[(er4~LHi6:Ѻ@ԅrST0trk%$Č0ez" *z"T/X9|8.C5Feg}CQ%͞ˣJvL/?j^h&9xF`њZ(&yF&Iݻfg#W;3^{Wo^4'vV[[K';+mӍִ]AC@W?1^{එyh +^]fm~iԵ]AB@WTk̏t uR?l.OIHiYyԶ]Aˀ7c:q}ힽaf6Z~қm(+sK4{^6}T*UUu]n.:kx{:2 _m=sAߤU@?Z-Vކеz왍Nэ{|5 pڶn b p-@sPg]0G7fy-M{GCF'%{4`=$-Ge\ eU:m+Zt'WjO!OAF@ik&t݆ϥ_ e}=]"Wz_.͜E3leWFih|t-wZۍ-uw=6YN{6|} |*={Ѽn.S.z1zjۻTH]흾 DuDvmvK.`V]yY~sI@t?/ϓ. m&["+P?MzovVЫG3-GRR[(!!\_,^%?v@ҵő m`Y)tem8GMx.))A]Y i`ViW`?^~!S#^+ѽGZj?Vģ0.))A꨷lzL*]OXrY`DBBLOj{-MH'ii-ϰ ok7^ )쭡b]UXSְmռY|5*cֽk0B7镹%ڽP#8nȎq}mJr23_>lE5$iwui+ H~F`IjƵ@q \ @#qG0".0" l`„.0! ,AQHN6qzkKJ#o;`Xv2>,tێJJ7Z/*A .@fفjMzkg @TvZH3Zxu6Ra'%O?/dQ5xYkU]Rֽkق@DaS^RSּ5|BeHNN͘p HvcYcC5:y #`οb;z2.!kr}gUWkyZn=f Pvsn3p~;4p˚=ē~NmI] ¾ 0lH[_L hsh_ғߤc_њec)g7VIZ5yrgk̞W#IjӪv>՞y睝M8[|]\շ8M6%|@PZڨI-m>=k='aiRo-x?>Q.}`Ȏ:Wsmu u > .@,&;+!!˱tﭧDQwRW\vF\~Q7>spYw$%A~;~}6¾ g&if_=j,v+UL1(tWake:@Ș>j$Gq2t7S?vL|]u/ .(0E6Mk6hiۺzښOrifޱxm/Gx> Lal%%~{lBsR4*}{0Z/tNIɚpV^#Lf:u@k#RSu =S^ZyuR/.@n&΃z~B=0eg뺆#,Þ[B/?H uUf7y Wy}Bwegל`Wh(||`l`.;Ws?V@"c:iɍL֯PGv6zctM̠':wuW;d=;EveD}9J@B(0iհ bvP1{\P&G7D޴Iy_$-Qjm~Yrr&]CDv%bh|Yzni_ˆR;kg}nJOIIwyuL}{ЌNj}:+3Y?:WJ/N+Rzd=hb;dj͒suݔ@NKMԄ jqzC5@y°hL m;*5ezᕏ=ep XL n?מ:r`۵tŤZ|1v`V뽧_csج'ߤ%oTuumk%%%h)uy]Nk[n 'b2 l.=͜E%gf$[c;s:V-͞WߤWh-j7]4=F-X]>ZLSi[Y*We;Zan(ӇW|e(HNNP5[= r4tP &0<pc#`vTNV GFqvTi*Tyam$ߏWyE*VJKMTfFw>'$-ؽ.Ho.8c"@DADADADADADADADADA~j*֘,N;Pi3599h=goضLgiJ5փy~}&Zd9p֚ e:|hL``b/d9p? fgg+%%hMgXosج, ΩOl0Zh=xdjLmhݻoO[g_l,8a]٭+ӧ0$I]c]:粹:Teꢢ"5a^Kgh,&= =՟^߶“ߢE ܹS J}I%:8 IDAT~,9/ʃPW'Mo}zNƍ쨓zPbNZ~^z=4mswg;5 Y~SVMRXUյڱRf?s:w ;6H:ºi5-maM&O3;1IKeamZh͛7+##v+c ~u~ca]GnF'ټL~PPPbn voC4R,ӟgg %hq}@#M4IÇ Oy^xMZx ) yOw@HkN˖-Sǎmb]X@n+i͖!++K3gd\$mt$^YfJ\8PRF)77Wא!Cl$i:@@_oG I{$# 8磌ŋ91A (Im7֭>}ߴJq7ޗt^ -[ԩSj*}%]&' -ɓ'ꫯVzzvB#;a 7@GxI{j޼ƌ.LÇWBB7`O"I$/@R @eee@۷>}0,ɒ2$53Xs|cS~rpTYYY} kHc %&k.], @ADADADADADADADADA@lT<%''*Lo^={رc5h %$+CnܸQ3fҥK}vUVVs9G R,_{xˇ3o߾;TTTd}馛]uuuG~iԩ@4bnvmvfϞ /Peeeq}}za I~,誫{UWW뮻}_~YƍSMMMYχ֝waw\ďcxꩧtEƍկ_?۷5@u?1kNׯWzz/wy>}zj3 k(ٺuq_Zvf̘:~ ABQ&r|!%KҥKgԞ={<_X-z !CyFUUz~ ABQIIIjݺW$UXXDٳZ~ ABQƍecW$<(~<RSSvZujjjԧOZQu@4 8m&&&jԩg$ď1h ͟?_{768@g =@`)))5o6m3)ѣƌJ;wҿUTT /KZR{~a=@0o<*狔iFɶ[ˎ;T]]OX@?K.ۈxN pppppppppppppppppPfl߾] ,{ァk۶mڿo5BTӦMӴiӴ|r DB2e|An!Dy'tkΝ[A $***t5' "!駟oaDnΝ:t֭[gDШQ06qD;@ x M6v(PiizmZ4ew"@̴ixf [~-Fٱc&IZ2|n!?$@{[HTɏ#@hȎI# _m(F /6Z3z'\r,r!;w2Z3j=~GY7"I$iI.p_"?pN`y DD?: _  Gÿab7J !Bx@0 Bo cG@`1C[@0G @`0C_u V1 aCX>W ` | `!<S `"<. `#c`?cAC4 ?c p#~@0?:08&_MQ1J h#?/`7;I  q 7a wQ A 1 Hp !#<8/#@1Ul7=S=K.4Z?E_$i@!1!E4?`P_  @Bă10#: "aU,xbFY1 [n|n #'vEH:`xb #vD4Y hi.i&EΖv#O H4IŶ}:Ikh @tZRF#(tXҙzZ ?I3l7q@õ|ۍ1,GpuY Ꮿ@hJv#xxk$ v#9 5 }_$c S#=+"K{F*m7`#%H:NRSp6I?sIՖ{Ap$I$I:QRv2$Z @UJ*$]<FO4IENDB`