From 27b2ff3745d18f4d436524bec11cf9eac676653c Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 4 Dec 2014 16:51:37 +0100 Subject: [PATCH] TMI-84: LZWDecoder fixes for > 12 bit exception when using full 12 bit (4096 entries) table --- .../com/twelvemonkeys/io/enc/Decoder.java | 7 +-- .../imageio/plugins/tiff/LZWDecoder.java | 43 ++++++++---------- .../plugins/tiff/TIFFImageReaderTest.java | 7 +-- .../resources/tiff/lzw-full-12-bit-table.tif | Bin 0 -> 11630 bytes 4 files changed, 26 insertions(+), 31 deletions(-) create mode 100644 imageio/imageio-tiff/src/test/resources/tiff/lzw-full-12-bit-table.tif diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java index 7bd83879..aa7576d0 100755 --- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java +++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java @@ -57,9 +57,10 @@ public interface Decoder { * @return the total number of bytes read into the buffer, or {@code 0} * if there is no more data because the end of the stream has been reached. * - * @throws DecodeException if encoded data is corrupt - * @throws IOException if an I/O error occurs - * @throws java.io.EOFException if a premature end-of-file is encountered + * @throws DecodeException if encoded data is corrupt. + * @throws IOException if an I/O error occurs. + * @throws java.io.EOFException if a premature end-of-file is encountered. + * @throws java.lang.NullPointerException if either argument is {@code null}. */ int decode(InputStream stream, ByteBuffer buffer) throws IOException; } diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java index 51fa49f7..bc47635c 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java @@ -33,12 +33,11 @@ import com.twelvemonkeys.io.enc.Decoder; import java.io.IOException; import java.io.InputStream; -import java.lang.String; import java.nio.ByteBuffer; /** - * Lempel–Ziv–Welch (LZW) decompression. LZW is a universal loss-less data compression algorithm - * created by Abraham Lempel, Jacob Ziv, and Terry Welch. + * Lempel–Ziv–Welch (LZW) decompression. + * LZW is a universal loss-less data compression algorithm created by Abraham Lempel, Jacob Ziv, and Terry Welch. * Inspired by libTiff's LZW decompression. * * @author Harald Kuhr @@ -57,8 +56,6 @@ abstract class LZWDecoder implements Decoder { private static final int TABLE_SIZE = 1 << MAX_BITS; - private final boolean compatibilityMode; - private final LZWString[] table; private int tableLength; int bitsPerCode; @@ -70,11 +67,8 @@ abstract class LZWDecoder implements Decoder { int nextData; int nextBits; - - protected LZWDecoder(final boolean compatibilityMode) { - this.compatibilityMode = compatibilityMode; - - table = new LZWString[compatibilityMode ? TABLE_SIZE + 1024 : TABLE_SIZE]; // libTiff adds 1024 "for compatibility"... + protected LZWDecoder(int tableSize) { + table = new LZWString[tableSize]; // First 258 entries of table is always fixed for (int i = 0; i < 256; i++) { @@ -97,6 +91,10 @@ abstract class LZWDecoder implements Decoder { } public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException { + if (buffer == null) { + throw new NullPointerException("buffer == null"); // As per contract + } + // Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992. // See Section 13: "LZW Compression"/"LZW Decoding", page 61+ int code; @@ -114,8 +112,7 @@ abstract class LZWDecoder implements Decoder { } else { if (table[oldCode] == null) { - System.err.println("tableLength: " + tableLength); - System.err.println("oldCode: " + oldCode); + throw new DecodeException(String.format("Corrupted TIFF LZW: code %d (table size: %d)", oldCode, tableLength)); } if (isInTable(code)) { @@ -133,7 +130,7 @@ abstract class LZWDecoder implements Decoder { oldCode = code; if (buffer.remaining() < maxString + 1) { - // Buffer full, stop decoding for now + // Buffer (almost) full, stop decoding for now break; } } @@ -142,18 +139,18 @@ abstract class LZWDecoder implements Decoder { } private void addStringToTable(final LZWString string) throws IOException { + if (tableLength > table.length) { + throw new DecodeException(String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS)); + } + table[tableLength++] = string; if (tableLength > maxCode) { bitsPerCode++; if (bitsPerCode > MAX_BITS) { - if (compatibilityMode) { - bitsPerCode--; - } - else { - throw new DecodeException(String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS)); - } + // Continue reading MAX_BITS (12 bit) length codes + bitsPerCode = MAX_BITS; } bitMask = bitmaskFor(bitsPerCode); @@ -173,9 +170,9 @@ abstract class LZWDecoder implements Decoder { protected abstract int getNextCode(final InputStream stream) throws IOException; - static boolean isOldBitReversedStream(final InputStream stream) throws IOException { stream.mark(2); + try { int one = stream.read(); int two = stream.read(); @@ -191,10 +188,10 @@ abstract class LZWDecoder implements Decoder { return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWSpecDecoder(); } - private static final class LZWSpecDecoder extends LZWDecoder { + static final class LZWSpecDecoder extends LZWDecoder { protected LZWSpecDecoder() { - super(false); + super(TABLE_SIZE); } @Override @@ -243,7 +240,7 @@ abstract class LZWDecoder implements Decoder { // compressed data will be identical whether it is an ‘II’ or ‘MM’ file." protected LZWCompatibilityDecoder() { - super(true); + super(TABLE_SIZE + 1024); // libTiff adds 1024 "for compatibility", this value seems to work fine... } @Override diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java index 7122b900..e440a2cd 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java @@ -30,7 +30,6 @@ import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase; import org.junit.Test; import javax.imageio.ImageReadParam; -import javax.imageio.ImageReader; import javax.imageio.event.IIOReadWarningListener; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; @@ -42,12 +41,9 @@ import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.contains; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; /** * TIFFImageReaderTest @@ -72,6 +68,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTestCaseDs3 zB47cjK@k&dfPkQgi0JeByYId4d-t3@^EtCKyLkaQ_&W&sZ-)M5*njh150J$F*aKrA%>U9${V&G-Uz>mbHjwoH*lV5# z$pU|8`_H;SFG%kH@i0jKfBCBYH-rBg9{B(K*XKX2_HX}>|9?sPK@Qq3X%4^>h?`uA7-bb+8lc^7aJA7lj_wPAz z_h_eotoQw+U2o!ZW>%&5Uzu7->j?^#-aEPZ`y_{&;C1e!xM)Ft$T8o$V$w3@gQ1Cj z`#1Ni*F6hM^1D}d*zDqHM6$n`@o|TyiKrC+2gdDz?Yx*{``*+}$KILsPd#_x{v40srByld!Fa%P}~iB-$=a%#%&xaqV9Z=a=^D;`v>gUo-+%nrIA zere{*R#w!XFLl!^KN__y@35Bo+WoxhlmoRc1GjvM2^6<}S*0LtJ0>xy zWItAULDg|wB2dS9yi&o$bprk2AZ4QBf}O`CI@Zl|vf_Y`_mueeVBe|o2jTwH;(74_ z)8#H{K|FEjNh+`Gc|pjGSWS88Ol9f0@bRg7%UK_vZnLOO}yZ_~^ ziNdoN0e22eJbhylI6D}4C;R=(T-vql8sc5md^PX;(D2F;(>e3Tcin%iFNT^aI-h@k*TPHtXhyVZ^@o|_L-k>P ztqy0yW`+-*5BFO#Y6yRY+RKXY*B~}VymFMj9O)k=dNuM@8M}G$UPH^}rB@G6wSD#% zyLfd}2=Qvw5T zPko=dL*u;)ycik2`R2iy*&p6^8FRb~|1zlQK?)i3Ta!;J-%m9JpYhF@%l2E?_OfeP znrL`*gK|9RjoIq2i6`8(v5QJW_E~Qa$ZY)fw42!+y?9bUIuU&S=k}lRC+j;eF23D5 zuk`I-Hwu!+!6VL44dHI1>Jy}Td>t!UoI-IJ;o{Y5sGZW3(a5LddehU?4JCI<(LgWW zzLIUI>OR`iYgzAG&Q8|xpbS0e!$*|TI!!!A7w=ltr=MbP96aQX?Ci%E7Bw2$`Rl1) z@2{^fXiRqVba(jYF}^u(zSGC^Wn`00eRuBsMzEKAQJbx8DgKE5u~IFRp=~Ez?nol2 zOrrnK6Vrtg?>f`GUoM`rtKZ7Hv~kkM19{utHjogiw^WJQ{muR^KL7G@)ygsJFGFT( zX`!8UzGIQ)LuX72-v5$2J;q!ZHnUGUYS`pA*1~lhI=uJ1W=F05P`{HywAT56?mGR& zCr(4hbJ5;?&JL&a&i4$SF;IW*Jao?B{PO5o1BX!;hkuMN$n(w_M2@=*-8Z@5 z%s+2XH0A2>)a*jm!Ucnt8P}oL`!BSuH5d%dx;cD4aA9`qqQT-@x1nDL8$8x0*6+#UDYG+e-6GK~D}KD^(q;W^>5VbQ9Gqmx6!Z?!9i zE$be`hn+5J>0UJ)+BoDG?Q${Dw8?Ps$D!flZWl{Q%|^&=Psda47a!QS7^(mE96opG z;tHkJ$Pw^z{Kt!>;Om09dQH#&K3L;7wM|$1##Pi^KgZbcYf$am-knW{Sr-zVjJodX zqn2v^xss;l_3?qe<1g6fZAyEs&;{T92v<#`0+lJ1$NEoYE0>R!AKY}r`dJr+#@raZ zrH*>$*Vz}&&STkirM@&c?pPMPr%A7?Ro=fpGAj0%LKm*T>9BQh%*stP8?p>87rcjX zP}lB=h#J2+Wc;fZpc$_I(PXOjF6j21~dU}k?cTWQ~(Ci;Y}5D(wSYHv^U=((=5 zPu`G?(nTt6jNu0c&%>8g_iCW$ zcGH0v2|%-;lsXKDx@NKZObtp2aZ0^181bA5r`+O`B=Bex0IGKcJXB1;^}D6+1!arz z8}tB9rs!o~>r*dLqMq8>9y#Wg<&-jIuDY}W883y3J#tXUHx*#_*i^D|_S@Dzx(M?1 zVsLRd1n(R!#1!Lc_TH>F;yD6rim&AIq`^V|Q1%U3i|{M<_0rpS@q19hZ?5f!lQa|r zo_36z7UJ;HL6=FzG%&48uAe~^zLa$cwDQCpQeTD$S*t|q*~)k2?_2<#cL5)(5JI9y zP#{m+gp7&ZeF+3SDY>#79f3)PW6oPLO+Z9Pu_CoV$h>5^E4dcUsV_l=))N3wDCDl^ ztP-^&7S2XF^o~wQ5t>-fSzUV}?S{JryqKc3J>y{=3^d5x7gHIe(ck3bcE56;2q_b4 z+kxGqyzCBppvC)yCMxFCD_5~=W&3=CKTDDfkT=w1hkSd5xt(S|?$)d3;)AfeyA$D* zMyVddK}l`}T6CT&MSr5+lH{AjU}22NNL?7mJ|IJ;5DJf|72Vyh${-$Gyb2tV_hQlsWez0O>zV0b&#ikbe=TLas&W%c@l+24|M ztqNE_CW`9oWI}v}vXS*VIcjEjCeL0(&+Xfv*+HQ`RvaiKM&a>R_p-yMn|s#&?kZ@z zF-D9_$3AcUqt09hUPyuyB*?y4zU|IXp(3Cw1nN*FI!Xy7!Mf-=4qb*^0s|*v*om0y z4v>~)Ea~B|`D84+P=X@~cF0m7P{G%#RLReL?aVN%#(PD zsUdlQX!U9|S-AumOr*USjU*+uq(*urD{%fZdbOHZhT_~q_ZCWmPDnmSVizqC+*RrL zVKo{`p1cdU_OX_sfe|QSmGh-Q7C7_~oP)L^DGE*`sc~^w#%bw_of-~LntWVnz7WIC znmA@!CFnu|@7KU3SVje=ys(nqtjLa22bE!ITJnn`QiKIeg^(Jz0j+yUu0VC8BY$X*g7M<}vX zAeEtjKk771tiTPOC|^Nek|igqGdyu@X~MCqrx|Mq(mblM9!m@PrLSEPtj^AqpzTL|ov0$bf|G7)kgd_|P@%6mWsG>0$}$ZQ zPLdHZt-nx8oj}kpYtX1tY#otrDka=hSzt+nkuLc+6gSvcLSDiE4>THCI1mgs_DN?^ z1P7WGv24Y0MWis}8bcx&LZu$K8AsTX8sB5=AS6TXnOAO>64+x6%uENjl-L%+9$z(R zQPN~loB^wxd{2&0ts!`UCj8PFPcLWukp*m&d)s8Gt(Ypd;lgzV00wUC!1G^8l8)-B z^((PUpsREZ!9y{DDDbVTj5r8#+tlDri4w(4Zw;3T1|dn#8iEaB!ZPgJ#wo_Al3Vp& zLN#jjy9J^_fg?NK+lc~7L_PJ31@plA<-NcboKR;I(X8ag-^-dukj~i_HYkxG*1h+H z3CXjbs1oidm<8L*?}q{_c7@eSg5M6x4~1zCsoM0a z&bq&qh!U58+*GBZN}!yFYnMcnXd=k5>cglK1feLXrUK39n&Ld0>1|-5zV7!b@wOj_ zQA}|*9d~*{m%JnlObpkiH-mam0#UpbSdKF*Hd!9AM2GS3iROyPu~sa<_cwFezycLr zZiyIs9~9Ts%s|O<9d)_7Sn_ebF0~d=ge1ek>>Yq)zrq_bVwXx3`4b2)F%+ITVEu|%HF6P-%c z;a7o;_9hfFl{l%wsckxfVFdkw=?Qf7rEm(C z9jXX8Be`D{=*l1yNc^s11;U!J>82K~5y?*+_uWboyvL9SVANbPcUA$!hg9z~<&4Ub z_buj3AQ>E78n+0puf&;^C&QrgSS7|Xh=ec|oRy^4B6B7rX+omZHM9CLLz;#h{e~47 zuSEXo1))oUPKb6eHQ6_Xsm3^UN)lsGobb&TEUU!*2;w131UylWT-f|Df;@#0-Bk}a z#WOD2=g2EjL6wjbctI4B=dVe26(iJTn@uEgXYo`U%}X3fau=_$AIF~)VuU;TZ70(F z651@W*jlkzfu&P%qAz^Rv@bWbshj&gNW< zG||c@q)mhoCra4bmyu%#T!&5Bo3rPTd;-q$o;cYYNj9&BYf2DDf*{W@G!NmLiM=3g zae4q|#^FlA-`nJ{8e|M3XhoTgX|T^D_`1^Rc})fSax@iSI@O%>N*<(jwYOfDFeX3! zaldS)A;=cb32f|pCdU?n`Fm^7s)V^a;`fyGX&XSr_ANVo1(2f*C$R9{m@ET~kcv=e z+(ZhVi6y$~pGsHp-!=rTN)mHma<=ANLuu-htIG8X)GP%7sI|p7N$}kV@DS7OH|GXO zv04I@YmtH~(X*+i;f_iIoD#VUpqC+~P4^2@v4G#H#tKoQw+LITs>xdxBEqR%C1{ zkN|)$rpQ789DN9ATb}V1#9ab7U66d4l$xnGfv0BC=}1h70~5CE`BER3RH3m~poGyZ@;FTf;u1&%eq zol@j(0UR|D0RyL_KtP7=J2gC!q{N?Wn2(dAe!fKW+O1AtlDCxv8;UGhCE_a(3op-o zrNn*((9SBbw-f_36&UZqfVKi3CB(tQQswoD4<*UZ;M8ZTe=mK|D(V%hvbj@1&?66e zrNC`e;480?-$SVQ+*3<8dzD^W+^BghnIynq4gm-1FiX}vP!T6wyx5(^6@#UI^; z#)EVkBox!X{}$n{=RS^k;~i-2MYiXC8;g1_O`c}422qDwG2qNOYq3}@Ayvox0> zd{v-*g_Gf*8$zoDOITuTAFKqZ zK4JWrAsNckaf-w>`PpuOZULbuA}t2J9bA?8g|h5R#=Gzew0=CdTLBEL9B0toItrb4 zOW8%0t1if8@?+fLw;xGafjt)ZPF7FNncWsb=~_z-W+)qI_{F4YGU|KnyChJA;?(kH<%;X{vAb_5wS!m( zNE&x`Wh)@aMA3RlANzEtEH$@iWHI^Y3y*c@$%&OC+Xo*0IO#p&g0q_wH~Mw|)A!8v zq1!T-Z!P~koPF@%ojHC?c;l1c@~`){AFW|wZK9uO#!ym)#~FZ|kgUimB{|3;EL%n7 zgspVPd=(3r<|b$rO1~Axx_PMDM$B#51*di_CK$i#NXw25aI7S*%=%b=8bX*ceoN6_ z?Q#myzMUImDcaj#s$yRi+VCBdPu-Rn#$Ye(b<%#RoKapik)i4IO|oo%zIe$K{4)dV zso9bc^c`cHZ=Ub=8QGyX_c(uRb1)l8`nJzr(MnhEV86kNu(jShQx8*j-8bTP9-4?z zTxoK(mc7Tu*!vKPkY{x++==H_9$LGLZW=?qG^N}pQxba&b9T$e`aJp`8y%ogK-rQN znSA>D@bo68f_$-$U_yI1C3-qc^_{7Tm0x>JU8H|YrOM$`;O}kU{cArwtT{zGgA)o6 zhdxLOR7bKLN*K>4z646=eJp9%Re^F2Dx3U}9o$?o8(-G0we#t1{hp238WrCJbN-gL zLgAi)!arhYdcN{IKJW1}OIa(XPieRBlvT78lu9=aEz`EE@yjm?zzE-svpK`DTU>yE zwDkJz6nC?v&)&%h%X!g|KkAj(*`!{Tgz)k%6fB9K*@|LzI60ogUFAH3FZq}Ce6QmS zG>j@;p$)4r??aBUS7IXPeT7MT9n-L%OuO!{Jiq$-fgUshv&RsAHzzV@p04olOO{iQ zJ)C%dU$)W{P~mYVI9~d3k{D~*j8ZZ*U-I>>2dyiDdh?9i?PnIXV15Yqb;WB=OUI+p zEPdpu%81=S!?()Sou#cVaJ6uSd`A8B$;q=PN?YWqHTxXrJWqjiX)v19FS(|1V3@Ne z!X@ZQ6G?ayfE4bvhmgXHprXFXa+ZTVV%#X<0`$5t+ErM#TNp9SYL^Hp)u>}zBK?F^ z3_EJ^1P%)(G;~}E;)`T#g^SP@n{ez=7s%L-F8BDWf{%$Z)>TzlpyRAgUynzA!E;HG zey8Hcc(0;$-5vu!YXej~C-*4JA1v-@2w)&^G6o^huDieob2tj6Ha&41%yTpG z%d5KJHY+&&dVmn?6xQQoI$l({wTrz#Eusb;wruW`W9}=#p>*sA$2*0Tvu=y&nq!{tQQ5muTQ9*-)(|q|Xn{Ii zODIR2hrjDL_51D-qp|$q_+28*CDxhN+QW~#PY4qSZ{-~8-9IzwvR4Y+CPCQNe2mx9 zRHhggewXtee&FBMZ=RG#qNq9nf55EKfT^=1fx`B;CBSi>Qv5D`=%tM7kN0)@P&fBm zA64F~9Akv6m!>*i$-SP-*I~TG`@r~`DCLGHQiq9wP_<0Vs?a~>9i9hlS(sa?78A7z z=?4bnd^Ff}N-?J?YFVmFQ@Pp_%G&l~Mw}D@d(2{A?+;|!KaeooXcE(PMlWXfsi#SO zD;%}OqF5UUPN;Vc>8u$vLirv-f2~qtQ{+iP1L85o6@~7&QcO*jZuFL((=ShMSPPS- zntFmT%?h@N8<0Jk>SgqAdv(|j4@Y9W%GP}m$_!U|HdP$uQH>j5MKspSU!m5uYI6|R z@!U?m8uH)o$mDZs+|a}D0QdZ2Tlq}O{*xfCt-<4ON$OnRFwyW$igkLO=(8qiT2FvF ztRr8M=&6!F)~uhaM#ey~ZpeytWqn1&&e@ha{W4}Zqw3}JGj~KqI#ko49qGb?XRRXiVN{-jg)E4# zF@FHU>M;F-zkvqe++^)|fh%_2)T|X8WtvMRV)l72d^naV!t4GZLTa|Fp^WN1wxO$7 z2^|7|e$#q$z)I%G**H3gD&KciM*UKuO0MIq^MHLW{KdK8=$!yFp|umrpw7%9j*u60 zvxkJ*zTt<&Q-ApK_q_t3BDAQM@sgx5Fnoml1wwq9%SNmiv z)*;A4(4(YxNsVCntA_o93VWGU(qsLaIA-Bqj@1~L_8qe?RYnGA- zy&Q9jFD54To9IE?d(E77bJpKT`A5=Fa zfh1Wxm!fRwF*}d%>g(_+C$xwoS;nxC;O@%nA2)jo*ICxq)I!2KP|TV~QZIX9Kz|JK zeHDixwATFRc zHD?$YMQ)a=fpRPMiT&;9%MmBVZrp_axM3j){2P#^yk{w>;+!OLDzlQfPoKFvO(WOb z47}553+KF40$gcW2{FkX_^h&0#>Y}V2dhz01m_vw8U06y;s=4eMs#Gt0hC!iE6*2s zUvdXE?wF%1(L*W9Yxd4fzzf&MuA{eROUJ50&5q?a(kWtt^Ou zKhV7l$A{^6D$^mq6(by&^^QFqMa%_=1+_JoN)zJzDVWtPrksF|y|moW>1;ll!_zme zD1y-g!J}fR4ofCl3buFgI09}yP~02?b+oh& z&{d4kfU^P+9m!0-=H0c{+~yGGy|Utj6zHyh``Rdq6c;Gkk;E^~r-XrDFBG$)AzMgd zcN3i8nOjY;)bv+Wivy1ubh3(>3&+7peJGX(vtloqbrrVYr0T3=>pq`DkYlQq2+@O? zp_pQ-7Hq-0AGiwRIUOkRyS=7WFz7WvDu(cnG0*NsMa1U%!lBfw`TRpBvl&JdCuqI3 zO~NTS#{m#hEpB#Y^0oA-SMo_gdNHB5*AjEO)dmJnF-MO<-YFJSjSDI$#njNe*%Gd2 ze1n=2NZ-G3&;m|1dC0p08#Doh#ub~oz+N}^|BB2*2SO#|3P(L*_V_#0Q}B&T$TcS+ zd+qLy%R*}6R?hLp^@$LwQJ-2#KF7TvT@I`Uru&`%5Ls}J5FHT%@X!^7K~Ut+VpB^{ zro*5?FgSu}x>c5M8V*lS2GC;kW+%N%JGXQ~WP$Z4qbGL?tBXhPg2N4y+ z5m%vrbC}QZ?$9$euuJX@Q z3CAHoJX2$%h%W)+9HNvWP51}s<3XnivoD5zE@CBuef4i*1uu~wnQGDQquu#yW(+F1 zi06=pa2qiqnAwL5`Py+Jy7SU4!I#&Ng&GBFAui58jn*u3_E()6&&cW6UsRt4YF1}@ z;a{FUA(Woo$LoP~c;|jQhNQa)p*_?eqxYeY!TC#xOVXiV}!$AoBTlkO|)u z{PeQ$Xi82u1Zuvgi0Yq9+GDGLA44t*@iS~H9>9F}(nkX{Ni{iyguK23q^e z)Z`=_fq8mPeCy7q+cS5+ypmuJJsN-a;*xbW~0DzCWL7P>;c`3Z3`FHIP(~r1_kfI>G1O{*b)KOM2Dp5eC&OEQ3 zUR=sg4?IMA4Ch-xx3U?MzZ(fvHt4AS03xBSbj@Uz5wZyj57A z3EBz=M~V7hY3(kwC^+p|lJ1vRm;y%wAePQdMWGODUjauWPk7EBB7pJ_<~gU9B&5L* z!TI&^a2^~!Do$@sg%2jcb-@s?JBM9vn-;2pAJ9q?l9;?uctk4Tn^n*u4A^7A56WI$ zVKwj^>5c;zG7}i4Lh$cJhh3%=n`2-E-DfUO*}HaxC^Gx=-YQt_KqC^Zz2+63?!XIO z!F=I4ueXmlt{^O8u5f`=5t^TLeXe`t4R@u0s6=6-(5Lu38^KrfXj z=O=`E+ajroaJ-;UdP@wdq&ND{uY-S`7J*o_K_myvZoH**CSVCMi4^j_a!qP z;Ar(6n%TF!WmVb>wB@&GNMAK=>j7=%8uF)bp$unAYR&=|GQb?T~Z$--D*#K*ELapj9(N+hoG^3|$E zed|Tr>PsJM7DG%UQ|m4%)-P7=jl6oc(e>P7uj1m=1$KVJ;#Y|!7>jk4y`&6UGQM>2 z>g6T(Z%biU8|Ipp7>iN0Ee+nSOE&~d18o-ut}O}1mp0qa4|IH%=>M#8^W4;}&*a;m zy>Fl0?D~AHIXb`ltk~Vp>@%Oc?w|4Q`TVlt^T&tv1CN$Lg)um8y+!Y`-pLr-{<^8h z%e0K;)F*Y`gUgjkF;|Cc|2$phM#oG&udRKtydD+{8?9A*xk3zHF&?iOm{@W5SqYn} z37TGEc*NGuRL{Lyx#6@jFk4;rW<_AVviY`Jo4+b?FivIu^!0bEWYbme52u4Zt{&4{ z&0nbcv$)FET@m=>-5BEGjtY#o%eE_>^kG^XsW z%sPj^KCQq~Rf;ens6A2qia~wVNKaJVH5a@m;-pGqylO<9T4K3+M7u^}``(Brnu#M? z5pT5w6O%`57WcS^iZ)UJa4jZ6I!AN?aCP5fAl zcw(APQjBVHnp;ZD!PK<)v>2cCwDOFY_{_BS<1qzUX(QP&7f+;pIT>?@o-WRa8Ocf4 z$&LBMOn1wR`8Pj3z93e*FulAeR;M_<{Zy=7N%}}>Y;alnm-5)OiVX3}*z&3jozt;R z)fsLzv5#sq;_G6k>odyF#C|!O(S9xtem-O5LYz`V#+Qq6Cag?xcAQ&drp~3f@XMKQ zSK>}y&5Un~t830IZ;5Mf&20ZC?nztb$hEk)*E7Gg$NlKQnC$*3eKP}oEB!}j`ls9J z(_QIL?xf%8PH(!KUUx6O;C^~qPkQ)+bf1Umc8}6cIO(d~bm`u7cwgF&{8~m9Z^=J4l0SVUuq7TbipOz#gfk}I=2 z^75xpzRj*GhtRPM>#O*GYn+ab{bUXtH8|@MHlES{F=c;)&4<&kps+lvM)!z`OdFLN zr_zzd>g_z+D~DRWq9!x@LoWDT_lchT1^@bv7?8L9)3u=7Z^8v zyg%n%#>3$F=^YsC)^RQ^ftO{gDn;xKS?BHK+wD0q@c!#e)d32FzA%bAP$c0PucC@aadS+s49zf zWoWFVMWb2&{58#|4sAEIo{nuF(tai>KUn_kkmgIn##`o-#?J=>W=vijI_I~~b^M-# z>4@a)qXTZIcdi|zVC658Db?oXR_@q%Lix0