From 7911ce4de9bdedfc2b49e9ef2a65ac630ce995d7 Mon Sep 17 00:00:00 2001 From: Mostyn Bramley-Moore Date: Mon, 29 May 2023 08:25:37 +0200 Subject: [PATCH] Add some 7zip zstd reader tests There is a popular 7-Zip fork with zstandard support, with releases as far back as 2017: https://github.com/mcmilk/7-Zip-zstd.git Zstandard support is not yet available in 7-Zip, though it is planned for a future release: https://sourceforge.net/p/sevenzip/feature-requests/1580/ This change adds a couple of tests for reading 7-Zip archives which use zstandard compression. They are expected to fail until support is added in the following commit. Relates to #1656. --- libarchive/test/test_read_format_7zip.c | 206 ++++++++++++++++++ .../test_read_format_7zip_solid_zstd.7z.uu | 9 + .../test/test_read_format_7zip_zstd.7z.uu | 12 + .../test/test_read_format_7zip_zstd_bcj.7z.uu | 56 +++++ .../test_read_format_7zip_zstd_nobcj.7z.uu | 56 +++++ 5 files changed, 339 insertions(+) create mode 100644 libarchive/test/test_read_format_7zip_solid_zstd.7z.uu create mode 100644 libarchive/test/test_read_format_7zip_zstd.7z.uu create mode 100644 libarchive/test/test_read_format_7zip_zstd_bcj.7z.uu create mode 100644 libarchive/test/test_read_format_7zip_zstd_nobcj.7z.uu diff --git a/libarchive/test/test_read_format_7zip.c b/libarchive/test/test_read_format_7zip.c index 3c72595ae..ca9074557 100644 --- a/libarchive/test/test_read_format_7zip.c +++ b/libarchive/test/test_read_format_7zip.c @@ -30,6 +30,9 @@ __FBSDID("$FreeBSD"); #define open _open #endif +#define __LIBARCHIVE_BUILD +#include + /* * Extract a non-encoded file. * The header of the 7z archive files is not encoded. @@ -283,6 +286,141 @@ test_extract_all_files(const char *refname) assertEqualInt(ARCHIVE_OK, archive_read_free(a)); } +/* + * Extract multi files. + * Like test_extract_all_files, but with zstandard compression. + */ +static void +test_extract_all_files_zstd(const char *refname) +{ + struct archive_entry *ae; + struct archive *a; + char buff[128]; + + extract_reference_file(refname); + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_open_filename(a, refname, 10240)); + + /* Verify directory dir1. Note that this comes before the dir1/file1 entry in recent versions of 7-Zip. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt((AE_IFDIR | 0755), archive_entry_mode(ae)); + assertEqualString("dir1/", archive_entry_pathname(ae)); + assertEqualInt(2764801, archive_entry_mtime(ae)); + assertEqualInt(archive_entry_is_encrypted(ae), 0); + assertEqualIntA(a, archive_read_has_encrypted_entries(a), 0); + + /* Verify regular file1. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae)); + assertEqualString("dir1/file1", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + assertEqualInt(13, archive_entry_size(ae)); + assertEqualInt(archive_entry_is_encrypted(ae), 0); + assertEqualIntA(a, archive_read_has_encrypted_entries(a), 0); + assertEqualInt(13, archive_read_data(a, buff, sizeof(buff))); + assertEqualMem(buff, "aaaaaaaaaaaa\n", 13); + + /* Verify regular file2. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae)); + assertEqualString("file2", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + assertEqualInt(26, archive_entry_size(ae)); + assertEqualInt(archive_entry_is_encrypted(ae), 0); + assertEqualIntA(a, archive_read_has_encrypted_entries(a), 0); + assertEqualInt(26, archive_read_data(a, buff, sizeof(buff))); + assertEqualMem(buff, "aaaaaaaaaaaa\nbbbbbbbbbbbb\n", 26); + + /* Verify regular file3. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae)); + assertEqualString("file3", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + assertEqualInt(39, archive_entry_size(ae)); + assertEqualInt(archive_entry_is_encrypted(ae), 0); + assertEqualIntA(a, archive_read_has_encrypted_entries(a), 0); + assertEqualInt(39, archive_read_data(a, buff, sizeof(buff))); + assertEqualMem(buff, "aaaaaaaaaaaa\nbbbbbbbbbbbb\ncccccccccccc\n", 39); + + /* Verify regular file4. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt((AE_IFREG | 0644), archive_entry_mode(ae)); + assertEqualString("file4", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + assertEqualInt(52, archive_entry_size(ae)); + assertEqualInt(archive_entry_is_encrypted(ae), 0); + assertEqualIntA(a, archive_read_has_encrypted_entries(a), 0); + assertEqualInt(52, archive_read_data(a, buff, sizeof(buff))); + assertEqualMem(buff, + "aaaaaaaaaaaa\nbbbbbbbbbbbb\ncccccccccccc\ndddddddddddd\n", 52); + + assertEqualInt(5, archive_file_count(a)); + + /* End of archive. */ + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + + /* Verify archive format. */ + assertEqualIntA(a, ARCHIVE_FILTER_NONE, archive_filter_code(a, 0)); + assertEqualIntA(a, ARCHIVE_FORMAT_7ZIP, archive_format(a)); + + /* Close the archive. */ + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); +} + +/* + * Extract file from an archives using ZSTD compression with and without BCJ. + */ +static void +test_extract_file_zstd_bcj_nobjc(const char *refname) +{ + struct archive_entry *ae; + struct archive *a; + char buff[4096]; + uint32_t computed_crc = 0; + uint32_t expected_crc = 0xbd66eebc; + + extract_reference_file(refname); + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_filter_all(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_open_filename(a, refname, 10240)); + + /* Verify regular file: hw. */ + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt((AE_IFREG | 0775), archive_entry_mode(ae)); + assertEqualString("hw", archive_entry_pathname(ae)); + assertEqualInt(1685913368, archive_entry_mtime(ae)); + assertEqualInt(15952, archive_entry_size(ae)); + assertEqualInt(archive_entry_is_encrypted(ae), 0); + assertEqualIntA(a, archive_read_has_encrypted_entries(a), 0); + + for (;;) { + la_ssize_t bytes_read = archive_read_data(a, buff, sizeof(buff)); + assert(bytes_read >= 0); + if (bytes_read == 0) break; + computed_crc = crc32(computed_crc, buff, bytes_read); + } + assertEqualInt(computed_crc, expected_crc); + + assertEqualInt(1, archive_file_count(a)); + + /* End of archive. */ + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + + /* Verify archive format. */ + assertEqualIntA(a, ARCHIVE_FILTER_NONE, archive_filter_code(a, 0)); + assertEqualIntA(a, ARCHIVE_FORMAT_7ZIP, archive_format(a)); + + /* Close the archive. */ + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); +} + /* * Extract last file. * The header of the 7z archive files is encoded with LZMA. @@ -782,6 +920,74 @@ DEFINE_TEST(test_read_format_7zip_deflate) assertEqualInt(ARCHIVE_OK, archive_read_free(a)); } +DEFINE_TEST(test_read_format_7zip_zstd) +{ + struct archive *a; + + assert((a = archive_read_new()) != NULL); + + /* Extracting with libzstd */ + if (ARCHIVE_OK != archive_read_support_filter_zstd(a)) { + skipping( + "7zip:zstd decoding is not supported on this platform"); + } else { + test_extract_all_files_zstd("test_read_format_7zip_zstd.7z"); + } + + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); +} + +DEFINE_TEST(test_read_format_7zip_zstd_solid) +{ + struct archive *a; + + assert((a = archive_read_new()) != NULL); + + /* Extracting with libzstd */ + if (ARCHIVE_OK != archive_read_support_filter_zstd(a)) { + skipping( + "7zip:zstd decoding is not supported on this platform"); + } else { + test_extract_all_files_zstd("test_read_format_7zip_solid_zstd.7z"); + } + + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); +} + +DEFINE_TEST(test_read_format_7zip_zstd_bcj) +{ + struct archive *a; + + assert((a = archive_read_new()) != NULL); + + /* Extracting with libzstd */ + if (ARCHIVE_OK != archive_read_support_filter_zstd(a)) { + skipping( + "7zip:zstd decoding is not supported on this platform"); + } else { + test_extract_file_zstd_bcj_nobjc("test_read_format_7zip_zstd_bcj.7z"); + } + + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); +} + +DEFINE_TEST(test_read_format_7zip_zstd_nobcj) +{ + struct archive *a; + + assert((a = archive_read_new()) != NULL); + + /* Extracting with libzstd */ + if (ARCHIVE_OK != archive_read_support_filter_zstd(a)) { + skipping( + "7zip:zstd decoding is not supported on this platform"); + } else { + test_extract_file_zstd_bcj_nobjc("test_read_format_7zip_zstd_nobcj.7z"); + } + + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); +} + DEFINE_TEST(test_read_format_7zip_empty) { test_empty_archive(); diff --git a/libarchive/test/test_read_format_7zip_solid_zstd.7z.uu b/libarchive/test/test_read_format_7zip_solid_zstd.7z.uu new file mode 100644 index 000000000..3bffb98f0 --- /dev/null +++ b/libarchive/test/test_read_format_7zip_solid_zstd.7z.uu @@ -0,0 +1,9 @@ +begin 664 test_read_format_7zip_solid_zstd.7z +M-WJ\KR<<``1&(FS)O@`````````B`````````$V+D*,HM2_]`$@!``!0*DT8 +M!````"$````HM2_]((+%```P80IB8V0*!X"P/JZA4J4;TS.X,9C'`#4``($S +M!ZX/T5NC)*"0H'?;G=XO-Y<-`+"F*K87M`OOZ1+1#31#M/`2YN,FY:(1).I) +M(B(+;9$W4?0*8=3V5N;BSZ)(0UGD/'LOSN"0&#DNX!A2*5\#&8IP$1G=7-]@ +MP\EE$]Z;/%6NU^\_,X:MD?57P#S>.+BINH?CHRX,::[Q5P*!X\DH````%P8V +?`0F`B``'"P$``2,#`0$%70`0```,@-H*`9/CZP`````` +` +end diff --git a/libarchive/test/test_read_format_7zip_zstd.7z.uu b/libarchive/test/test_read_format_7zip_zstd.7z.uu new file mode 100644 index 000000000..acd49c421 --- /dev/null +++ b/libarchive/test/test_read_format_7zip_zstd.7z.uu @@ -0,0 +1,12 @@ +begin 664 test_read_format_7zip_zstd.7z +M-WJ\KR<<``2QPP_C,`$````````C``````````+H!E8R#*L]U;J=* +M@K`Q6]^1F4MZF33TE2LJK8X@%5MY%P.#1*EM98=_,9"?K'$*_A0YVC)]1ML= +MK-5-,X82B;/_L`I&FQ!5HP#H````%P:`J`$)@(@`!PL!``$C`P$!!5T`$``` ++#(#Z"@%\$`,4```` +` +end diff --git a/libarchive/test/test_read_format_7zip_zstd_bcj.7z.uu b/libarchive/test/test_read_format_7zip_zstd_bcj.7z.uu new file mode 100644 index 000000000..2a75db897 --- /dev/null +++ b/libarchive/test/test_read_format_7zip_zstd_bcj.7z.uu @@ -0,0 +1,56 @@ +begin 664 test_read_format_7zip_zstd_bcj.7z +M-WJ\KR<<``0N5BP@P@@```````!B`````````(*-K]$HM2_]`$@!``!0*DT8 +M!````*T(```HM2_]8%`]'44`ZE%D$DIPW'$.RP,C]5>);DM31:I?D,QMZ#'[ +MX]O[KCOFX)J*\$44=%&TV"+)8#QPV28QFR]0!V-3JH9UII:164M``!FT!`!L +MXELXL[8O4Q,!'@$2`5/J6F?&6/][6'!>ZPF&:U6C8%?F9DH.#=>\Y71GN:,M +M^ZA,>'Z0J2/,U:!!Y(?S^B3?P==^[[?]YK&,Q.+\7;C(4KFQ#"L]4*LEI'SCU\6R?O,=IH;BR_, +M^=,]F?4+_I]'MWL=@)3B4LF]J`%84^*+!@]ERRM(EKDF0@0*\*,A13M[J_;, +M*JBF":I,JT3`(:"1UI!6,G[AH1;N$0&++]Q;5RDTUW:S#KB?&TWF[K] +MTO%\G]%M-\G(:E8U$$&Q(;$WDZ;I?H%#_\%^!#:>[<]:V>W_LQ-_Y?Q`(@"A`L^!S]UHDBP&X#+X<) +MS4GK>LP.H+]9:E#78]`VV'FC.[!1K?U2*3:2NB93]/].==UT^O\>==W,\/]? +M==V44-=-'0;;@?WP+*-()<@P[!['GHC]HU%G3KK6_QP>N.7:3':M;%ATH6"X +M5K.NM0IE?OG9C8W8_ZH67)-_%.SL'Q6>_:M>@EZKKIMUJ5R"7&M5A.0'M[;4 +M9V?&P.&\UIV>1F5MLE<;FI+U2(R2M=FUI@DE%2V)P>4ZMO6SVU+C=&8,(,YK +MY>EH%F;<4AEF?GO3?%`"2[7)S_F;T@5U>S"M7+I6DE0Z2*&^9T( +MJG_I9W?UWQ,25(-=_:/IEZ/P9_??RQ%4__=P%-5CYQ*N-=P)*8F#4Q-_J:5U +M'?(JJ";.7XGXA`058FD):LB'3TA23)R_445Q[&_\["64C9^%I)EM.SW%L.LT49QN)R:^3(SM^H7ETE.]UGXV.Q +MG;]%EO.7U5^?C5.OJ[*+Z*C1$-(A?J%;#=`G\MY'Q2V%>VHZ_YM'O9).+?@] +M&[B#>O9PXX#A62$P9N#(I\W?">*#9\:C*[Y`E1!]GR=28((]^C4"J>WK;[__ +M:S)_$_HE*_R&?+\TV>-DL>I!>D%)J`P3US430XU<+-S +M),83X)<`RFY;.%N87*>=0>!SX'OZ_PMXW8W.*B=`J28#S1D*U77O4Y3UMK+_ +MO\$[$]BLC/9N@+Y/YWVH[O3MW+GVQ&Q`R,C:,)F[_R=SFIU!UC7+1_G_FTY: +M8Y)D#["R,A(5%88*A3Y0-$W3'+='>?"2_FWPSMRM12UGXO>_YX^R,O;,`86% +MR76-YO\_I%]?Q!O0\0A\!KO'+A(I&]6GZ:+ZQA?)QLIV;H]'5WUY5M7O6FY7 +MW^C2,I:J?H>S6-B=U9]Y++_T[)Z8.U))Q\I6_2/-'H>(24CM.]3";[*H[+.; +M)<]9:C+&4MTNQ^IGS6Z/W^/<:?6+;$L+^>0INN&\C!Q)+"(-?'D)(%2ZL3O]\]"#?E1$+0VXIS="D"#8T@4)AH;OS0QK*@.S(L,50<_CA +MSLQF9'9?XSB;;-U94YD.FO]*997K@$8#<$+3;FE`!=:DQF+2IM`^P_V)X+H8 +M>CA6D83\V2'4'&8019V9GJ;4N^GH!]DY[/[G+;OB1_1;1C*]PB'S([P%1P=2 +MVZWP^0AVC4J*P$8O6>4N-N<4YQ@52=LB99,=Q-92\YTM6_:!O(CLS*.3].4# +M"=G'`&WN&'Q@PN!^%WP*@ +M>Q#R';%'W>W,J`@NVM/IYT(1Z9WO[L0DR.YFB5_M%B-Q;^"AA"&\S5O?^]2G +M5:0N[G64]G+ZWR@D&V)Q@^LXPO*B9G9F,9'#!BQ>,6;6FU$N?3 +M`.Q/U+S_$GPHT*;\)<@Y@CF:$OL@/;(&MBC.]2FU)]]1H%"W*G?%/.,%R +M%[H<>8MCK*V1.J%.C=7'N/;E*:U^2SX@)@'SYZ\;36III>#+-E0=MARSYX'R +M\WH,G%TT4TXJ7V:1?#Q/HER'4TFC.'Y%Y0D*MH2(6\B5YU*21O#?4OS=K,#> +MM?@F6*^:37;#*>NV\;VYS9R567I^EN(RXY3MF[\:?IS1%VRW7Z[:^VI'LY=_ZBTR<)4IT7]IYY]1OK&'@.&E"VR@9%YH&S'Q,#9CF] +MG8&_K//(.AY#/&&R\L5Y9G9*M">BYP#J+4'9&3=D:QBW=45`IG&!W"1D^=K7 +M3-\)$&`N%0_$JV$!\![TG-`=:",BG5.9A)"?V=NSXPVO21_O-QG<+$FCO"K< +M+7,ER$BPT:CS!0`Z0BV^S#)QZ3PK0-/_L#+OU^QUO<'=-9BN6Y:-5!LY:B;N +MZREX8;-G[,XTOK;@Y$4Z[)9[!"MHPI^1M7KK1KL45B0C''SO8H,4XUG<1RU\ +MG/6=<8^3]'MPL\V\;ITR,#$MU@E[S7%[`00&``$)B,(`!PL!``(D!/<1`04! +M!0,```0#`P$#`0`,OE"^4``("@&\[F:]```%`1D*`````````````!$'`&@` +@=P```!D$`````!0*`0``'';'*9?9`14&`0`@@/V!```` +` +end diff --git a/libarchive/test/test_read_format_7zip_zstd_nobcj.7z.uu b/libarchive/test/test_read_format_7zip_zstd_nobcj.7z.uu new file mode 100644 index 000000000..c6fed0beb --- /dev/null +++ b/libarchive/test/test_read_format_7zip_zstd_nobcj.7z.uu @@ -0,0 +1,56 @@ +begin 664 test_read_format_7zip_zstd_nobcj.7z +M-WJ\KR<<``0`N.\QQ0@```````!2`````````#TEXKPHM2_]`$@!``!0*DT8 +M!````+`(```HM2_]8%`]-44`*E)P$DIPW'$.R^E\_]^S[,>DG"("L:I:;)%D,!ZX;).8S1>H@[$I[8=UII:164M``!FT!`!L +MLB.<6?N3%!4!'@$2`4JZ6"F]QIDPV'_>%=P7BW[I8M4HT)&YD9)#@VO>T')( +M6DXDLR#Z0.9.,$=CQA`?#OR3?`=?^_-\VY];GI%8>_\;%UNJ5YYCX1,5_?(: +MQGKIU]U/5,3]112,U@GKA"U4(CHU_>]?%'#,>[\,M\C-Z9;7VUB$ +MX[V_YLFL7_#_N]H]V0'(*.^4G(L:@#4EO&CP8+:TNUZ$?T.]HBVDK*<$SDQKRB7Q&.>2PKE>9]N=98W(:M@B]-TWPM'=H;6Q(O]U]F!6[+-I!?+9H57 +M^J6+/?-BJWKO6EZ`=&0CSO\U"XY)[]U%Z[W[H/VO78!?;`X_\UJW`+O8J@C) +M!W)IZ2AGPL#AOE@>GC:%97*R-C1)6*0P2=BF%YLVI&2T)`*7+!D7D$Y+Q^%, +M&#_<%]O#T5B@<4LE<,G^"R#=>E7FGS?5\P2X9)WXF+XA55232Q>[Q8LE,<6+ +M",9W0JC_$I#NZI\71*B'7?7>MW@Y!H%T_SP<0OV?=\.HDZ$!3DE*')R:]DLM +ML>QP5T$U[?U"XE,25(BE):CA'CXE23'M_<95Z\AA&=Y[OQ*)3+K[1;;TU2MI +M+K];V=)TN^'>ZK[\C;.0L_&WDNVNZ9F[.VZ+XD7R_<;VZ2O*:T\;' +M:OF&B^W>/Z\^3ANG/H?*221';<;0#A$LY6J0/G'W/REN:NQ3V_D_=]0+=VK` +M]SE_IY#/.3QQONRJ$"`S8.35YO,`Z?$],CY5\5_@SV#Z/T^DP$S#/OT:`=4& +M]K<___N8OPWYDA5\1#V?FN1RLECU0+T@%#;UTN!TFEU-._L:5`$.$\5%W>12 +M`S<\2&,]`7X)P.S&A;.)S3W-#(*?`^_#_U_`Y]SD6.4$"&LQ4IWA4/]CE/4V +ML_^WL4,3V*R.SK-!^KV=[Z4Y:._,H2QOS/:CK*P-DZG[_S'H&0J$9;$\E/^_ +M[:BU)4C.`5961:*BQ-)@L$>:JJJ*X_;ICIW2?XT=FKNQJ.5,_/DPZL/,K$5S +M06)B<^]G_O^#"@9&O`4=C[]G<'+Y12)GX_IU75S?""-;6>G2[O'XKC_3Z_I\ +MZ^7K&]]ZQEK7YW%6&[_T^O269[BFWQ,T1RIK6>FN?ZS;(Q%1*?TL^N4R?*1YO7ZQY4AEO?X+GZCJNKCWN_*[WWWKY)9A +ME]>;`]&5V^.>:W>WO/V6M^Z[@WOHI)1$M:F&4TQ,'':"4:CB>C%")2(21)(D +MQ1RR"R@01$'(&+EY$D!C+-E5AA&"G!$1$1DI*"HJ*%3:#M)%"5G$G\"8%:>G[^>!M\V$XK!3)(B?F)-CZ=K`("^=H&,T+K]/R8B +M['>&V!;.[RZK*AXHW/(1&>W$L([TS6UB*(17-)#`^=N<-Y!+LP+,;VWOM.:V +MG"H>@K<%',>.&QD-=+)!XH+%'R\&]".3\%"A*"Q49ZE[Z/-`S&!3[$CTBVC3R]ZNOP";Z'2 +MH2O_5Y`ZL)EB)470T:OT>2R/20[R[(Y,@NY,>;K5;&0GE`P\E#.%]WG4O +M&5+K/5T3')<)O"I,1MVZ"]]^(&Q5B">>RVD\[W$PP\;7(AE#%.%E-HDY"IIT +MR+8L[$1_("Y?)9Q7F&M +MK64ED6D`V29*P=.*;'(U%I_))$V/)_?,$=;YBZAX0W2M@PFKU%9S3_T.T:#< +MW/V$HY:ZT^6Q"C":"ARLJ?J@S!>.02=/K6Z%TS_J/3*YY=UH8DNK;;"^H=ZX +M?#9L*(R;]X<0VT7;[:2U;!;IBO(DRB.[\UAYLS,UO/S@RN?PVG?_*AAX1GE0+O-\:J] +M7,:5^[ASH4F3NG>,TSNX9AQPQ>"V$LT4["&]0W=&EFLO#1^W/A*04USP-0!9 +MOO8][Q\"!,VAXT'A8CF`N?FP)O*.V0A/YEIF(B0G]^KN-F5KPD;:J[TX$J21 +MW@+<%;CB-NJY,<;P!8#[&A:9S.V+U9\ADL)_&*EW<[O +ML2P=S_XO.NY4@\-FS$OP8EX>6=[`00&``$)B,4`!PL!``$D!/<1 +M`04!!0,```R^4``("@&\[F:]```%`1D#````$0<`:`!W````&00`````%`H! +3```<=L