From 8c976ac7f0f17f617d483827a8b53b01ddf5d398 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Mon, 26 Nov 2012 13:25:07 +1300 Subject: [PATCH] Substantially rewrite AMF decoding. This is tricky, but we should now handle a lot more corner-cases. --- libmproxy/console/contentview.py | 88 +++++++++++++++++++++++-------- test/data/{test.amf => amf01} | Bin test/data/amf02 | Bin 0 -> 286 bytes test/data/amf03 | Bin 0 -> 33691 bytes test/test_console_contentview.py | 31 +++++++---- 5 files changed, 88 insertions(+), 31 deletions(-) rename test/data/{test.amf => amf01} (100%) create mode 100644 test/data/amf02 create mode 100644 test/data/amf03 diff --git a/libmproxy/console/contentview.py b/libmproxy/console/contentview.py index 2e93727a9..2b46064a8 100644 --- a/libmproxy/console/contentview.py +++ b/libmproxy/console/contentview.py @@ -15,11 +15,10 @@ from ..contrib import jsbeautifier, html2text try: import pyamf - from pyamf import remoting + from pyamf import remoting, flex except ImportError: # pragma nocover pyamf = None - VIEW_CUTOFF = 1024*50 @@ -236,30 +235,75 @@ class ViewMultipart: return "Multipart form", r -class ViewAMF: - name = "AMF" - prompt = ("amf", "f") - content_types = ["application/x-amf"] - def __call__(self, hdrs, content, limit): - envelope = remoting.decode(content) - if not envelope: - return None +if pyamf: + class DummyObject(dict): + def __init__(self, alias): + dict.__init__(self) - data = {} - data['amfVersion'] = envelope.amfVersion - for target, message in iter(envelope): - one_message = {} + def __readamf__(self, input): + data = input.readObject() + self["data"] = data - if hasattr(message, 'status'): - one_message['status'] = message.status + def pyamf_class_loader(s): + for i in pyamf.CLASS_LOADERS: + if i != pyamf_class_loader: + v = i(s) + if v: + return v + return DummyObject - if hasattr(message, 'target'): - one_message['target'] = message.target + pyamf.register_class_loader(pyamf_class_loader) - one_message['body'] = message.body - data[target] = one_message - s = json.dumps(data, indent=4) - return "AMF", _view_text(s[:limit], len(s), limit) + class ViewAMF: + name = "AMF" + prompt = ("amf", "f") + content_types = ["application/x-amf"] + + def unpack(self, b, seen=set([])): + if hasattr(b, "body"): + return self.unpack(b.body, seen) + if isinstance(b, DummyObject): + if id(b) in seen: + return "" + else: + seen.add(id(b)) + for k, v in b.items(): + b[k] = self.unpack(v, seen) + return b + elif isinstance(b, dict): + for k, v in b.items(): + b[k] = self.unpack(v, seen) + return b + elif isinstance(b, list): + return [self.unpack(i) for i in b] + elif isinstance(b, flex.ArrayCollection): + return [self.unpack(i, seen) for i in b] + else: + return b + + def __call__(self, hdrs, content, limit): + envelope = remoting.decode(content, strict=False) + if not envelope: + return None + + + txt = [] + for target, message in iter(envelope): + if isinstance(message, pyamf.remoting.Request): + txt.append(urwid.Text([ + ("header", "Request: "), + ("text", str(target)), + ])) + else: + txt.append(urwid.Text([ + ("header", "Response: "), + ("text", "%s, code %s"%(target, message.status)), + ])) + + s = json.dumps(self.unpack(message), indent=4) + txt.extend(_view_text(s[:limit], len(s), limit)) + + return "AMF v%s"%envelope.amfVersion, txt class ViewJavaScript: diff --git a/test/data/test.amf b/test/data/amf01 similarity index 100% rename from test/data/test.amf rename to test/data/amf01 diff --git a/test/data/amf02 b/test/data/amf02 new file mode 100644 index 0000000000000000000000000000000000000000..ba69f130a3c376da517780358308a687c25cd395 GIT binary patch literal 286 zcmXYrT~C8B6oy+x7ErS+Wbw+M&{;mp!sURGkZ4TAKY;XzjnER>Eqducx0~Lb_j#YZ zIp`80gpnf;!+vfl(Ptp3W5nYJ#~nLdjdL z4s!?Io8sTAZV(_%&F?s9YoCSSZw+d`He!TV^(c<8{D79jtb-HMrX44D(SyO>6?Sf% zrCYOYwX2GVl5HFogzl1zuskja4a+Rek}zUv>fV3akGL8en2(|Zw=QT@#589lPONLq nkgz1CB&_m%SwwkOu-rlZHTKG;=I%m@VO=(R$tNYG!Pr5+b>~m1 literal 0 HcmV?d00001 diff --git a/test/data/amf03 b/test/data/amf03 new file mode 100644 index 0000000000000000000000000000000000000000..d9fa736a2230b8f02582b54a887d6b18719a4faf GIT binary patch literal 33691 zcmdUYd3ambbtjj{ClL@#!!S+LG)>ABEzuGwuA(SgF&{yIM2O7*NQrV{_>p)dQGo!B z2TG!t5h-Fwfu=br7Jd+tR^c}Yo0X^DA%_x?mYAj$KwR7uJI#m8#e zwrwyt@>02Jsrr)triM589C*11G7CB|G4^Hfrtmg{EK zn(pXqG-Z{g6m=%sCPWZkloRo4Mm*USpGtJ367$J8s2)#^MJH2ZfCg2{Cp=Ovb6x+6gI@6c?lr$|<=yiW!(BQ2MxPFs6jc61GTmE|!=TCl}0O3`jPd5EaRD#6eB;EUMEI zViRId(j@N|=OCZ4g*jQmppq#mGO*Awmyq!#pT>10%Q|m08))i?6O|+N@;r3I2@ygK zAz0{<=O^UJWOR;j&K`IT@uJEydaEN*uyGQ?`Qy`n&|gx*wNyYUu@7(Q>gqhu*~OJ` zou=ikK7-H_8@G{E230|7X5!-p#TXCJ(0h~8qy&XwhlYZ3gG^0I9!ZwPX$j(}C?RJw zmXeaqlFt0Y5>rglVT^flJ|=>hm5Gs(0Z5f3rkDU*&^JNz&$z(}=5WZw zC$2~<*a}~Scogc9NTE=xYc_F0($JAr{lR#lS=FsW$!KIODn~)v79yBaOgiUN^GQiT zMDEF;=BMdo%;8Uf^%oqOR~|7MAC#sbIoX)EX2xm+-a~qvO+HYSv*_3oiOMw5?Ck*Q zYY0jgjocy6p*HAg9Cy7kZjF$;RmEqdUWb8+vOpQAUI_P|j7(CqkT{`Fc7q(9or_7} zH)8NaayMz%n3Ob*5wbA5V23s!zDY@QOhj8qm~tnn13H26OKxqGt$}gjd@34ibGj4p zX)}~!Pbb-yzdzYuQc}j-rWj8Nu^90!|H&xkP(%)KtqA~q;`Wxvh>u1xK8xOSa z?{05QzuwqO?;CsC_xH3nS~?Gyx_YuLP&BpBrm3jBY&76rdm2u_eWhUE@K_pZ< zx8?FgC8>Mfe8d+BhPY0y+*GX#SedDo<2f@|$(2IFGUZ$Z4>Y1vki&A0fN^Dfr3e0j z$#yskKPGlz<0#7 z*i>Nr&Y)uQ8YXAN(cERro32svdU+)$Me4~MI&=DAO*&Qgc3WI}T zyTeUyEgm5-;t1i}PLIotCV_E7&7H%p!9j;N9B>5Dh20TEJzK!x7hD0CcgVD@o&i@l z)+5fDS}RLSO+ENWEHyPLv!TD-7K(k%&__g6x4hbPbn;KWM#vYfKRV5^>p@2gG)-;AH(=Pm_4(gn~K*;5E z*)`;-&=PWZ{BG)s8a4<4&v@7)ctIhL!y8f|)G!EkjM-(+DhdSzWk}SO)#C`*hXvM( zp6l_|6?O#uAcaD4D&5ISv#CV0V%7#g5A_V7fbLu&P#!QVP+I334LJhnF@(VrC3qCp z*o8pIHxv;3!!%rK4VE1f+-&p&(QJ1MUaHRF!8$PNat{*ADMYCPUpmmQ_9(dR!wwJi zL=bE3Unh2t)r!;T zQBJXD=BfF3M6M+Nap~?VczDxNN`@M;8_-dmjkbyTXpEfewgKuFTFFM-5+3ro2TSqM zZH39!VQ9?;Lt{}J)erc5!BAM^fKsjmZB2`|?B$f=x3)R$K+LK+rd}&-X^b2;3n7ek z#w0CDZ1A3vDJnL{NTb`P;9L1l!c{c0p`-U?h5b6_1Rs9sjLFt(z)yulfb7&8#?>0M zyBuD-Bb=>j^#O7vWu+Al{7x79U;@iQFr&+vX*`zW!?DKCG1l2Pzi-X^- zTPlCRH#ll1t*uhRl{a@DzC)WGy_sqXv*+iX|MG*Ouikq42PQ_rJfQda9RZjo!5#KG zpvGNej<5rA9tws*V}BNS;adBpz*&WLJbdkgqtJaYq0j(>5brzR{WjilrC-Imc1IW6;G$l{50LY*`4~M?Ir%hD;6utvmf^H3VQ|ndJxUJ?CylG1!q`_E)@f z9iZ{ahQ1WaCil{zoUZpZ{S_LKSS)=EUAFv*3y;+0QYh2qQa{F$M?Vnvvj5p-k?9*|P!(J3SJF@kYY!Xv@xJthRn#tPvv zH!Qi=H72x+!U27<^0yr1n$bz%>EWv?S-s~=H zg1y`C7VLv?Y{HN`$3VabBVF-MdQ-qnWY5DJ0?lTpD!%~a#$b-G-TVFjPEbMF*5HT_ zph96dc|MF;t8)XuEec>bU=Z^m_{#%A&TJZCFQu|zcCjXofI?+>apOAJ{=QAen+KL$ zWR;1QPFFl|?lft%U)*4t>QtnUGS4y7tpCY=22~T9QGV$ZrZ^)ROS`fdb)%t`mGOs1 ziJlIxR{$;H-Fjie#zqWe{pN4b=UD@&w!)y6w^zKAU-|xGD)$#td19-TQO`gc2QN76 zKCch%IGk*k7rrL=QC(K~9X|N%TcRCUv1`;lY99^`JG|pANcVs*ptFUpxD~aQi^9;x zDCq0BtzvPXV}ypY;RIgaltFj0g$W1(O$t?#qf0kp91MQ%;OKBzaEAh;u45hpzx?Mc ze#ss9?jf?u&Qb4RkhDe^?zUo3Op`ke4xE#DuqP3@HBU2NSxzP5Qsp#4!j?HHflyCn zG?|Da#6tgDJw^YR@*NAK7?)zc{JsI7eV9=SHeg`ftNGb9tE^FnX^-MZ-*8;5gM!=Za)gfwFli$$9a_ISstvSD zv$m`Zh7pQ@ZVQcXX>8CMy`y6ew+jn80_*pU`HuNUbgR5^_QLxq_ItW4f29~d((2Kh#j4;k%?gGt&m=;Az5QfRJt-B3vbnyKz^fUMh z=bDx`T2(DSQqh0wrkJ(}qaj~dS%f&k6#Z}sZXMw|AxJKUw-YM{`$#Uz%M%$)F)@3y zD(ko^;5Sem%lGS2%vU>oN4w!qW2vzjBzRWMnVLG7{ym$I6D;SWz)4sLg#`OZmP5xk zz>9ZzJ%Y>K8Fo8{f}6p|NcZPd&mf@=wxCAI@E_k~=)ei25J1aWujkOVE3bhL^MTDQ z3g{bAwIp9lt2alWGYk#kxdFTuV_h9of1Y^p9c(Jx4reHgh0tKw<#i%%20NK?wj8`o zFD>Ob=mL|Aug`9`89L;~XV=ydGV&J1Y^zI6eHFrczdPQ477SO(?cnZY1@C!&S1G9A zEUe%U+J9Rq*v{>sf@^oye^x0lD+TYZKTPXP)<+3z^6l^3uM}8dO9_ghpp5l#w=JU7 zup#(Ko$3YExdm1f6o~a>XPM$A2(bk)8j2Am%gaqoN@R-m(J0A4ixUaz-laP#rz)Zm zn-(&XD}=v&=3n+a0_35;v(ib zbRch=i%wSIHAVI+Q;54&PGYNJx!XFY)R)0U{C?=H5g57<3@lu71XGzY>Mku~l;ign ze2=KrgImE`4cu9R_J9w8)ux55G%^fzmo|JmFn`3FR5#2xziAx4*xUWgFDv1GTEd@4 z7zGR^DTI9ygpFdqsg&R87ed3(4UpIlW-$2XOo=7{G**hlxi&*@AQ4&cgRaC`WO?}W zH}Tb(zD%?Hxr3%hxeGH4H^pVd-%|8lQOb8@YASeWuz9TAgu(ErA0hBTOoFl&HH_sd zjir<;=j+GDgTA0Z%X{x=xJ#R%T4Qmk>6{Zt+|O|M>YPfSKYxTjf0vOy*Ofjwz9AqS z8ypRV;hww3N5lHG*JYKfy%wPQ-G*LUO0-@(MiB{v-{yiZ6$+09g+Z@t#5b1dzO`_L zjp5uwaF_#B=ugEvKN@iS{eSqiFIV(`>1V(Kc$_Bh%rIkV}Ugh-h1}1-awCfcjbCO7@_BEosii%ZgYn8^qN8B zbsSD1VJQ++RFAADSx^QBMW5@YV&XKEgrXewvxF19Js9aEdez1)Rn^&z#s7GH zY+MOc7NZjW%?q*Wp_!$7Dh%=L?9TRVwX=PyE4$RDw=gYHK0+qEz0{6JEux);nQ7@@ z&Oo(5^3wgrI5BaT9;{{&Wi3cujv%#RsHP?7Mja04bhtr?x*RcHLx7(PV zL5w1jjEbf%C5c4Km_4=_K_=23tUfq?F;;47=C@%Zv>a1i%Jt}ah&NOF?#y$2`d89d z(qBklM$Xex`t9tk9zwazjz6krT7&2rOH+V#Dl3E0E7cJzGo7CyQZ82NMSN}gv+387 zTJ_fYTKb!&MGLnJ*jX5M=i>@y`!bkRBfgl59`EewXwMYZql?d_pT~dCrJqTE-gJI0 z2W~mwmgm5&4kRXzdnr#@Ca5;90{$S;UYRI_S<#Qe0B0lIPz!ND5HFg^wc`CZ2G zHGyt#ub*9CY6XUGgMf}09|$;(9>|52PsH3|)gQ*VuB6{ezm~qde)cdyyblm}U}!6x0%b?0S4#+M z3Mrl`u=*(n$L9%dz#2Itt3pzyn2a*M*vs9*nHXO%KpU`rKv^DJ+*o(eK5Hf=k2jw_ zA;nK;aH$d|v1FZHP*6b@Qak=zOq#l~^YwL=}liECi_`%2O>X_}f@@zfylM9H2u zks4y{G+SwVloH9AsYNHMHBJo!@me_5x{9+#)E@Tg35IB+`_kvmRKt8C*OfHda(fk} zlEft0im@?^ymEV;)&WDfYF5#XR%ZDmNv;R*fJB33crI>-6Q@&~qeBr`<&+^0QZEGM z`I?ZFnm>75bV>^%l9I^gaUkP8%;Fs{HObk5jm&Hi4HX4l#V|-D>wv$KtV}ekW!O@J zIH((_QJ;pe{ZT{t>+nJ5VC>JI$tXpj2b8l!Yo*_`ax=d&0PemePli^U`bdBq3>h3DGf7b_Zz4`v)SO02p?SrS^!k70K zf8+hdZ@$0yB`SRUxer#p{=qA21pa=zs)mK40{BTGKD3 z-vAIYIt21^`c=r!lLT@XSfemVt00G@K=f$Q!eaqT#l0e^Ka>7k zqxyw>vtK1}P2l{yvYb1E-m%$d<|85y-CRZ?jy3HRK2ZiQa^ZdcGgn z-@JhhzVL9dU9WVS&Y#(!x0RyxU9IC3R=_yAM+i$%Km zFP~XyOg{%ETv>lC{YE1m3D`XltkMm}(%_Fq=AyCq!blY6LrfJ5(t-uwmGz4R<~Eqw z8jPs``YRx2MXy05!T z=VKcg%avWlc4ry#g>aCA25#cuLKkM9s^Bcn?-tLRwdto|yoj`C6~C+pFt8s`h`1JL z?$iw@(X`T-yD+i^BrqHTh|@HRCTPx^7=0MCpng^q>jGCSg=*7J6aMSx$?YQuJ23`g zkOD%s9J@j9rp0Q$2FVIY?Ql_AQ0`bNk4lL+0l^9{YZw=C^1vX?RaCE|v+D-n@)nU6 zL7u<@ogmx=sj?LwMh%5ZW;=R1`ieKeY^!e({JUt)jD0&^VA7RAH>leVG-%7vT2+#3tgif)lS4MOzSB4Sg+fWOV z8RSIKwyd7qI6#1I1{AjTxeGqyM$6*k(IkjgG^g%vrVK{Pwi9qxAxy^*nrS#Z$1(F+ zVD`<&6S2H-eAnT2MpG(s1P-dXplTBfXs?m4;Krka#)Nq7P}v^K@LFv zgjiz|-Fa~8M%+tU#1^8I#L+;$4rxxm$d+_UXcRUy{VgKgZZ&klMACH8nZ?^x(C*Wi zreX)RH~o1q-g)rD#1#gpZ#sEq44q6m@l zwi6;Q7+IK{>7=gB=+(2qUT~&Ywhel2VsXYy+l*4(EZfNqD9Id_85U~Bj5I6f#*jT7 zf?VMk0oGz!2N4-EZLBxi45Z{Ys~V5}d?Y zxmtOkJu;Ttfevu`mS1k??^6!1g;{<*jsXq&JYgldE9@R~^Y>xr17Ziwb(x6*R@IH` z7ZpidN1(D5qp1qHLT*R6$F$hNuW$-_i#-`Q6$Izv96z3ZfhqH45-3`qH$sTfF``?q zr>``^A%8->=l}{u>AD)pY9626HyG|RUE=e)uIT^Zb|kM*hEHT($94EA#?Y2AYV342_O!xDc)O8?iwduj zab;To3}YfESQR@Ovn1rz*ql^$y5MTTPsyYd@fR~GS%`*XBcO%1&UVPSz+pjDWrhvt zVk8&)4F7>IE+XzoJ{M1@)lL=aEyD^IPxBYg@E1R=)V_p-Oq|?C zjx$p6Lyp0${tFOMnJEwv^k!IH+MWxpKr5Z}mrU7&tX+Q+A+=Z2uYvZ=I8y9;FH`q< zIbj_p-g~S80pBkr*3B) z(^_Xe&es$y#>gW-zXB6~P!Jt+! z`h~xIkqW$qg6ml|LbiWyqyingp*{cS5h~Cr8u;5kU^_{|f1VZS zh@pTr(F=y&|M82|hq2&nR>5%J-Pg16c=u!p73i3v#?`DQfp<@^CVHpdAN}>&tR~~V zm$Lf^-CCVp>4At{)pAk>EX5YvKl&(j|wB#v+zK{RVvVH-2a^~_fvu1$N0sU zvio?r&CF`(O&)fSuo^nmXa9I#b`$y2yRT! zJR1B{TFP5H+7IFXUA!P{%=3(F&@R2KqDV_Z{l$(q3E97&SUO zGX>p;Isu?BQ-B`PL@%g9lb%Bz2f&;_R!NIR0#KKb<+IUcVr@>!rO@v+te=mQTdLhR zhU_a`e1f`$u>)GJ3Q@vIWp2_C1k5Frw=9M$WkyaT(4e%S3V0WeIu_P{~!6lU^ls&}{i_g;6%-l<* z+;7LYFV=FuImP{2kA_Prx2=p@NthM|(;_ZKfXC^;`l1$lAr+jNkisNdlqW)}CO8*G zu8g|X`C-vo{!dlh;&WA4Lq2{j$vt*ExAcCWh8QAZW!!}ZLhSJ!KuEp4jKd@XA@)QF zUApbV&(_QP!6HvYxy3h+axe68zuA<*0r!`I04fT*;RjoEny@EAVL#icc!kyt@J{K?w%+=srv?DMS@Zn!j zYd2ydH}YGbynfuDs$M^_)9lin}|r!#KH!*kMm62Whr#zF} z0Cvj~IntxF_er-I({@MRV#+!)>Kz)zk#fC5TJTfWCu_u)!oWfsQywFV%QA~Ce9z=V znk*GU=a>GGu_C2LW;(0t?CI<2>gYjt^a4&E=6~guQoRNpUZQsNDdSh~p`6ur(jnyCWzYsXeVbtF!r~~nKl*;~B7sBNXIxm%Ebi+24m!8PHHE0gxs4IqkBgKOiT&`*{ zG&kkHHGqa6W@OaN+1f&OXWLMMZB!z|XCupF5f&1-u_wc2xK-0aAoIfIIU17MuFj7cmsbBf1c z*?8U>kTcTVbD;NNUq^fI!FIzwpgMu9ElptQ)*OfZZ4Vcm!0rPb2Rk~qI)VOwe-mgB zy_?htG{|*1^wK&J z#B`4{9kB`{dxI*)emZ#-r8Zn<7#{Eq`NovuM}IFtZE=OLHmb+vpH(cseOgD1AJ4p__^g^;I-q96oqeua87W5jQ7p>bQO7%Hz8LI>&LV!tXD1 zcX#z|DwV=}e|k;?8nC;d8@clz)^p1k8-pE%7hj<((flufymh-geHE4s|)whM^9ZR#Vt8*)2w^*6u$S!mHZ{oBZSjtQ@ zili_*FJ_tJ6+HAz@Bg@e3{b%PCw{)XpUFb~ki1`*%kBUVZfZB`@>$rx;5Y`j z3H#R3`S8K1=NgCCf~Ra%X!-{^FK3R6Wj5 zZ^7d{_?td(N1kfz>gqkv-nCVSWesEeC{$y{o6A;>KM#-7;B+(;nn69t*SO&fZWyC# z+<5lPk+|I3t#i2+pkghG2vBuA#TT5m?yl|wec21ijTZ`Q+`qqRGl=C*b=nLvZ?HRe zW7?LxlI$RI`*mxcS7%0F7lqT$$AwI7BJS627-8j@2jqcL3cB$}QW0H|8rXu#ag5g4ME z0phvzR~vh=@nZ~o?4uysH`bq~2uxceD!q<;IhJ8&Nq-JDj_we6!WbsOxsZ-V{M66e zjomPibo`Ef*=OH@My{0IenO6>Pi79AX0aYVmWBr8SFv$IZ&Rbh^#-xYn93Gz<&)Re=C5&&Qb{GMJe-8NO!+f77RCzk4-LC0g)vU0nx^&SC`;dNP^` zqJ%-3?cYGtGJfrrk4ChxaJshllS%ZMtr556EDG;^K2E=axLJ_KAUp<**aXqq)L`z~ zRQ8(l-%^Uf-M{tJSzvPyw~Lum189S|6*z}Eh6CGkPh_+R$p0N&V zw`wxIkI6J0oxrv&QuXi``yUjPXi~k&{fQr9uK9Y(BMpxsix7FT+9K_XEr&pPe(f$r zINayoze-hUFV?SJsj7zw!xc ztH>@6&CE&>Q5DY%cfCn9*=kdTbm>!nN44vjICo`>`s2In_3G+E^Qmj%S-pC9cJ-&< z`7TvgGy{Isl5P&t)kgc*pP_~pugfzqiX$_mjP|Yl8I_rFxwzm}ihlC^?_WicMx!p< zo$T@c8!ayXy#9e6^p&%?gu3WF*h4?>r)?EkOt~T5Yqj4EmN0Fh?iBr-E-i)k-umG< zhP;us*i;`NGj-p`_|AFYs}oY zP^j;{U;c%&AZjPqnETeCvLQbktey(^-mPF3I4Ep!PB&n@gJt{c$`O-Nu#b`+P7yph z18LI9g8&mdCD+l{*Jn7zL}$n-Jn$BJL_#l}^u;*A$lt z=V#v}unhQ<^qqbQk^C&Qr`s`bRtMVqO$#2e@%1X@@FU)UA5lc9n(+T>BL;!PaTKDU zws_A{;uS8&H5=%h4g)`p2Gr!6l{FwD>9xn-z61Qmuc|SGC;w&~L^gOqnq5#;@G}eF zx{CMNhCtP~&n$kxx>93{`~UEV)D?cR4Ee#hY8+Sg1{9cW?Ss@wwen^_pks-I%R%pJ z)B+h4>QC6Q(nAlOquOhSw8megN@gf+X+bIKI5W-qqrfEnFq#IZ`}giGp{mGXh)6fm z%Fq1%(?3G(9HFi1>g+n$*_X3fn=7@~TKAASffx7jk49>L?jb*eTpFYY8%9jFDEEr} z&X1CAXz*)vzX~}Gy2H0auV9d3kZkLcXlp$@?88qa9&V-8%we2{)P*lccTb3?7h>6g zjEwF+z(nlPz0liFt$z-ynWwOpc`E(v-o}U9KHZr93QWXHJ9qB1v>tZjDy>ALU&Ie) zij7`e+ME;*x3Z{P>tXy}Q*h*PYh{BfB1eAjOh4F26Or*B3=>yrLAZyW{W5zSQFw9k z4D)E#x|PRRAB#L|E1cNSKU}4@Q_9;%Z~HcToKYTSi^Az4Z-9B?OYnvTY zSH*Ja^G=47x|Uh`yYmbu)sLW_O7k=vns$8usm@?t>C%TJj?b4sYb!WY*Za&DOtw+qRzGRCs3YQ)jjCTbzX2 zWL6L7yv3obyep9%`~&*0S7;k+!Hs_;4D0IpNwQy}