did not work and sometimes stole file contents.
Add test for this case.
libarchive 2.7.* have had these following bugs.
1. When length of symlink name is longer than about 70,
it cannot get modify/access times recorded by "TF" extension
and maybe next file is stolen a part of its contents by
that process.
2. When length of file name is longer than about 80,
it cannot get modify/access times recorded by "TF" extension
and maybe it's stolen a part of its contents by that process.
3. When length of file name is longer than about 100,
it cannot get file mode, user id and group id recorded by "PX"
extension and as above.
4. When length of file name is longer than about 142,
it gets wrong file name(shorter name) and as above.
Condition of stealing file contents:
if file->offset - iso9660->current_position < file->ce_size
in next_entry_seek function, that process consumed file contents
of which were data of extensions.
That process read wrong data whether that process consumed file
contents or not.
SVN-Revision: 1513
libarchive/test/test_read_format_isojoliet_long.c \
libarchive/test/test_read_format_isojoliet_rr.c \
libarchive/test/test_read_format_isorr_bz2.c \
+ libarchive/test/test_read_format_isorr_ce.c \
libarchive/test/test_read_format_isorr_new_bz2.c \
libarchive/test/test_read_format_isozisofs_bz2.c \
libarchive/test/test_read_format_mtree.c \
libarchive/test/test_read_format_isojoliet_long.iso.bz2.uu \
libarchive/test/test_read_format_isojoliet_rr.iso.bz2.uu \
libarchive/test/test_read_format_isorr_bz2.iso.bz2.uu \
+ libarchive/test/test_read_format_isorr_ce.iso.bz2.uu \
libarchive/test/test_read_format_isorr_new_bz2.iso.bz2.uu \
libarchive/test/test_read_format_isozisofs_bz2.iso.bz2.uu \
libarchive/test/test_read_format_raw.data.Z.uu \
int subdirs;
uint64_t offset; /* Offset on disk. */
uint64_t size; /* File size in bytes. */
- uint64_t ce_offset; /* Offset of CE */
- uint64_t ce_size; /* Size of CE */
+ uint32_t ce_offset; /* Offset of CE */
+ uint32_t ce_size; /* Size of CE */
time_t birthtime; /* File created time. */
time_t mtime; /* File last modified time. */
time_t atime; /* File last accessed time. */
struct archive_string pathname;
char seenRockridge; /* Set true if RR extensions are used. */
char seenSUSP; /* Set true if SUSP is beging used. */
- unsigned char suspOffset;
char seenJoliet;
+ unsigned char suspOffset;
+ struct {
+ struct read_ce_req {
+ int64_t location;/* Location of CE */
+ struct file_info *file;
+ } *reqs;
+ int cnt;
+ int allocated;
+ } read_ce_req;
int64_t previous_number;
uint64_t previous_size;
struct archive_string previous_pathname;
static void parse_rockridge(struct iso9660 *iso9660,
struct file_info *file, const unsigned char *start,
const unsigned char *end);
+static int register_CE(struct iso9660 *iso9660, int32_t location,
+ struct file_info *file);
static void parse_rockridge_NM1(struct file_info *,
const unsigned char *, int);
static void parse_rockridge_SL1(struct file_info *,
free(iso9660->cache_files.files);
while ((file = next_entry(iso9660)) != NULL)
release_file(iso9660, file);
+ free(iso9660->read_ce_req.reqs);
archive_string_free(&iso9660->pathname);
archive_string_free(&iso9660->previous_pathname);
if (iso9660->pending_files)
}
}
if (iso9660->seenSUSP) {
+ file->name_continues = 0;
+ file->symlink_continues = 0;
rr_start += iso9660->suspOffset;
parse_rockridge(iso9660, file, rr_start, rr_end);
} else
const unsigned char *p, const unsigned char *end)
{
(void)iso9660; /* UNUSED */
- file->name_continues = 0;
- file->symlink_continues = 0;
while (p + 4 < end /* Enough space for another entry. */
&& p[0] >= 'A' && p[0] <= 'Z' /* Sanity-check 1st char of name. */
* 8 byte offset w/in above sector
* 8 byte length of continuation
*/
- file->ce_offset = (uint64_t)toi(data, 4)
- * iso9660->logical_block_size
- + toi(data + 8, 4);
- file->ce_size = toi(data + 16, 4);
- /* If the result is rediculous,
- * ignore it. */
- if (file->ce_offset + file->ce_size
- > iso9660->volume_size) {
- file->ce_offset = 0;
- file->ce_size = 0;
- }
+ int32_t location =
+ archive_le32dec(data);
+ file->ce_offset =
+ archive_le32dec(data+8);
+ file->ce_size =
+ archive_le32dec(data+16);
+ register_CE(iso9660, location, file);
}
break;
}
}
}
+static int
+register_CE(struct iso9660 *iso9660, int32_t location,
+ struct file_info *file)
+{
+ struct read_ce_req *p;
+ int64_t offset;
+ int i;
+
+ offset = location * iso9660->logical_block_size;
+ if (((file->mode & AE_IFMT) == AE_IFREG &&
+ offset >= file->offset) ||
+ offset < iso9660->current_position) {
+ /*archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Invalid location in RRIP \"CE\"");*/
+ return (ARCHIVE_FATAL);
+ }
+ if (iso9660->read_ce_req.cnt + 1 > iso9660->read_ce_req.allocated) {
+ int alloc = iso9660->read_ce_req.allocated;
+
+ if (alloc == 0)
+ alloc = 8;
+ else
+ alloc *= 2;
+ p = malloc(alloc * sizeof(*p));
+ if (p == NULL)
+ __archive_errx(1, "Out of memory");
+ iso9660->read_ce_req.allocated = alloc;
+ if (iso9660->read_ce_req.reqs != NULL) {
+ memcpy(p, iso9660->read_ce_req.reqs,
+ iso9660->read_ce_req.cnt * sizeof(*p));
+ free(iso9660->read_ce_req.reqs);
+ }
+ iso9660->read_ce_req.reqs = p;
+ }
+ for (i = 0; i < iso9660->read_ce_req.cnt; i++) {
+ if (iso9660->read_ce_req.reqs[i].location > location) {
+ p = &iso9660->read_ce_req.reqs[i];
+ memmove(p+1, p,
+ (iso9660->read_ce_req.cnt -i) * sizeof(*p));
+ p->location = offset;
+ p->file = file;
+ iso9660->read_ce_req.cnt++;
+ return (ARCHIVE_OK);
+ }
+ }
+ p = &iso9660->read_ce_req.reqs[iso9660->read_ce_req.cnt];
+ p->location = offset;
+ p->file = file;
+ iso9660->read_ce_req.cnt++;
+ return (ARCHIVE_OK);
+}
+
static void
parse_rockridge_NM1(struct file_info *file,
const unsigned char *data, int data_length)
if (!file->symlink_continues || file->symlink.length < 1)
archive_string_empty(&file->symlink);
- else if (file->symlink.s[file->symlink.length - 1] != '/')
+ else if (!file->symlink_continues &&
+ file->symlink.s[file->symlink.length - 1] != '/')
separator = "/";
file->symlink_continues = 0;
struct file_info *file;
uint64_t offset;
- *pfile = NULL;
- for (;;) {
- *pfile = file = next_cache_entry(iso9660);
- if (file == NULL)
- return (ARCHIVE_EOF);
-
- /* CE area precedes actual file data? Ignore it. */
- if (file->ce_offset > file->offset) {
- /* fprintf(stderr, " *** Discarding CE data.\n"); */
- file->ce_offset = 0;
- file->ce_size = 0;
- }
-
- /* Don't waste time seeking for zero-length bodies. */
- if (file->size == 0) {
- file->offset = iso9660->current_position;
- }
+ *pfile = file = next_cache_entry(iso9660);
+ if (file == NULL)
+ return (ARCHIVE_EOF);
- /* If CE exists, find and read it now. */
- if (file->ce_offset > 0)
- offset = file->ce_offset;
- else
- offset = file->offset;
-
- /* Seek forward to the start of the entry. */
- if (iso9660->current_position < offset) {
- off_t step = offset - iso9660->current_position;
- off_t bytes_read;
- bytes_read = __archive_read_skip(a, step);
- if (bytes_read < 0)
- return (bytes_read);
- iso9660->current_position = offset;
+ /* Read RRIP "CE" System Use Entry. */
+ while (iso9660->read_ce_req.cnt &&
+ iso9660->read_ce_req.reqs[0].location ==
+ iso9660->current_position) {
+ struct read_ce_req *ce;
+ const unsigned char *b, *p, *end;
+ size_t step;
+
+ step = iso9660->logical_block_size;
+ b = __archive_read_ahead(a, step, NULL);
+ if (b == NULL) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "Failed to read full block when scanning "
+ "ISO9660 directory list");
+ return (ARCHIVE_FATAL);
}
+ ce = iso9660->read_ce_req.reqs;
+ do {
+ p = b + ce->file->ce_offset;
+ end = p + ce->file->ce_size;
+ parse_rockridge(iso9660, ce->file, p, end);
+ memmove(ce, ce+1, sizeof(*ce) *
+ (iso9660->read_ce_req.cnt -1));
+ iso9660->read_ce_req.cnt--;
+ } while (iso9660->read_ce_req.cnt &&
+ ce->location == iso9660->current_position);
+ __archive_read_consume(a, step);
+ iso9660->current_position += step;
+ }
- /* We found body of file; handle it now. */
- if (offset == file->offset)
- return (ARCHIVE_OK);
-
- /* Found CE? Process it and push the file back onto list. */
- if (offset == file->ce_offset) {
- const void *p;
- ssize_t size = file->ce_size;
- const unsigned char *rr_start;
-
- file->ce_offset = 0;
- file->ce_size = 0;
- p = __archive_read_ahead(a, size, NULL);
- if (p == NULL)
- return (ARCHIVE_FATAL);
- rr_start = (const unsigned char *)p;
- parse_rockridge(iso9660, file, rr_start,
- rr_start + size);
- __archive_read_consume(a, size);
- iso9660->current_position += size;
- add_entry(iso9660, file);
- }
+ /* Don't waste time seeking for zero-length bodies. */
+ if (file->size == 0)
+ file->offset = iso9660->current_position;
+
+ offset = file->offset;
+
+ /* Seek forward to the start of the entry. */
+ if (iso9660->current_position < offset) {
+ off_t step = offset - iso9660->current_position;
+ off_t bytes_read;
+ bytes_read = __archive_read_skip(a, step);
+ if (bytes_read < 0)
+ return (bytes_read);
+ iso9660->current_position = offset;
}
+
+ /* We found body of file; handle it now. */
+ return (ARCHIVE_OK);
}
static struct file_info *
test_read_format_isojoliet_long.c
test_read_format_isojoliet_rr.c
test_read_format_isorr_bz2.c
+ test_read_format_isorr_ce.c
test_read_format_isorr_new_bz2.c
test_read_format_isozisofs_bz2.c
test_read_format_mtree.c
test_read_format_isojoliet_long.c \
test_read_format_isojoliet_rr.c \
test_read_format_isorr_bz2.c \
+ test_read_format_isorr_ce.c \
test_read_format_isorr_new_bz2.c \
test_read_format_isozisofs_bz2.c \
test_read_format_mtree.c \
--- /dev/null
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * Copyright (c) 2009 Michihiro NAKAJIMA
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "test.h"
+
+/*
+Execute the following command to rebuild the data for this program:
+ tail -n +32 test_read_format_isorr_ce.c | /bin/sh
+
+dirname=/tmp/iso
+#
+rm -rf $dirname
+mkdir $dirname
+#
+num=0
+file=""
+while [ $num -lt 150 ]
+do
+ num=$((num+1))
+ file="a$file"
+done
+#
+num=0
+while [ $num -lt 3 ]
+do
+ num=$((num+1))
+ file="a$file"
+ echo "hello $((num+150))" > $dirname/$file
+ dd if=/dev/zero count=1 bs=4080 >> $dirname/$file
+ (cd $dirname; ln -s $file sym$num)
+done
+#
+mkdir $dirname/dir
+#
+time1="197001020000.01"
+time2="197001030000.02"
+TZ=utc touch -afhm -t $time1 $dirname/dir $dirname/aaaa*
+TZ=utc touch -afhm -t $time2 $dirname/sym*
+TZ=utc touch -afhm -t $time1 $dirname
+#
+F=test_read_format_isorr_ce.iso.bz2
+mkisofs -R -uid 1 -gid 2 $dirname | bzip2 > $F
+uuencode $F $F > $F.uu
+rm -rf $dirname
+exit 1
+ */
+
+/*
+ * Test reading SUSP "CE" extension is works fine.
+ */
+
+static void
+mkpath(char *p, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ p[i] = 'a';
+ p[len] = '\0';
+}
+
+DEFINE_TEST(test_read_format_isorr_ce)
+{
+ const char *refname = "test_read_format_isorr_ce.iso.bz2";
+ char path1[160];
+ char path2[160];
+ char path3[160];
+ struct archive_entry *ae;
+ struct archive *a;
+ const void *p;
+ size_t size;
+ off_t offset;
+ int i;
+ int r;
+
+ mkpath(path1, 151);
+ mkpath(path2, 152);
+ mkpath(path3, 153);
+ extract_reference_file(refname);
+ assert((a = archive_read_new()) != NULL);
+ r = archive_read_support_compression_bzip2(a);
+ if (r == ARCHIVE_WARN) {
+ skipping("bzip2 reading not fully supported on this platform");
+ assertEqualInt(0, archive_read_finish(a));
+ return;
+ }
+ assertEqualInt(0, r);
+ assertEqualInt(0, archive_read_support_format_all(a));
+ assertEqualInt(ARCHIVE_OK,
+ archive_read_open_filename(a, refname, 10240));
+
+ /* Retrieve each of the 8 files on the ISO image and
+ * verify that each one is what we expect. */
+ for (i = 0; i < 8; ++i) {
+ assertEqualInt(0, archive_read_next_header(a, &ae));
+
+ if (strcmp(".", archive_entry_pathname(ae)) == 0) {
+ /* '.' root directory. */
+ assertEqualInt(AE_IFDIR, archive_entry_filetype(ae));
+ assertEqualInt(2048, archive_entry_size(ae));
+ /* Now, we read timestamp recorded by RRIP "TF". */
+ assertEqualInt(86401, archive_entry_mtime(ae));
+ assertEqualInt(0, archive_entry_mtime_nsec(ae));
+ /* Now, we read links recorded by RRIP "PX". */
+ assertEqualInt(3, archive_entry_stat(ae)->st_nlink);
+ assertEqualInt(1, archive_entry_uid(ae));
+ assertEqualIntA(a, ARCHIVE_EOF,
+ archive_read_data_block(a, &p, &size, &offset));
+ assertEqualInt((int)size, 0);
+ } else if (strcmp("dir", archive_entry_pathname(ae)) == 0) {
+ /* A directory. */
+ assertEqualString("dir", archive_entry_pathname(ae));
+ assertEqualInt(AE_IFDIR, archive_entry_filetype(ae));
+ assertEqualInt(2048, archive_entry_size(ae));
+ assertEqualInt(86401, archive_entry_mtime(ae));
+ assertEqualInt(86401, archive_entry_atime(ae));
+ assertEqualInt(2, archive_entry_stat(ae)->st_nlink);
+ assertEqualInt(1, archive_entry_uid(ae));
+ assertEqualInt(2, archive_entry_gid(ae));
+ } else if (strcmp(path1, archive_entry_pathname(ae)) == 0) {
+ /* A regular file. */
+ assertEqualString(path1, archive_entry_pathname(ae));
+ assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
+ assertEqualInt(4090, archive_entry_size(ae));
+ assertEqualInt(0,
+ archive_read_data_block(a, &p, &size, &offset));
+ assertEqualInt(0, offset);
+ assertEqualMem(p, "hello 151\n", 10);
+ assertEqualInt(86401, archive_entry_mtime(ae));
+ assertEqualInt(86401, archive_entry_atime(ae));
+ assertEqualInt(1, archive_entry_stat(ae)->st_nlink);
+ assertEqualInt(1, archive_entry_uid(ae));
+ assertEqualInt(2, archive_entry_gid(ae));
+ } else if (strcmp(path2, archive_entry_pathname(ae)) == 0) {
+ /* A regular file. */
+ assertEqualString(path2, archive_entry_pathname(ae));
+ assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
+ assertEqualInt(4090, archive_entry_size(ae));
+ assertEqualInt(0,
+ archive_read_data_block(a, &p, &size, &offset));
+ assertEqualInt(0, offset);
+ assertEqualMem(p, "hello 152\n", 10);
+ assertEqualInt(86401, archive_entry_mtime(ae));
+ assertEqualInt(86401, archive_entry_atime(ae));
+ assertEqualInt(1, archive_entry_stat(ae)->st_nlink);
+ assertEqualInt(1, archive_entry_uid(ae));
+ assertEqualInt(2, archive_entry_gid(ae));
+ } else if (strcmp(path3, archive_entry_pathname(ae)) == 0) {
+ /* A regular file. */
+ assertEqualString(path3, archive_entry_pathname(ae));
+ assertEqualInt(AE_IFREG, archive_entry_filetype(ae));
+ assertEqualInt(4090, archive_entry_size(ae));
+ assertEqualInt(0,
+ archive_read_data_block(a, &p, &size, &offset));
+ assertEqualInt(0, offset);
+ assertEqualMem(p, "hello 153\n", 10);
+ assertEqualInt(86401, archive_entry_mtime(ae));
+ assertEqualInt(86401, archive_entry_atime(ae));
+ assertEqualInt(1, archive_entry_stat(ae)->st_nlink);
+ assertEqualInt(1, archive_entry_uid(ae));
+ assertEqualInt(2, archive_entry_gid(ae));
+ } else if (strcmp("sym1", archive_entry_pathname(ae)) == 0) {
+ /* A symlink to the regular file. */
+ assertEqualInt(AE_IFLNK, archive_entry_filetype(ae));
+ assertEqualString(path1, archive_entry_symlink(ae));
+ assertEqualInt(0, archive_entry_size(ae));
+ assertEqualInt(172802, archive_entry_mtime(ae));
+ assertEqualInt(1, archive_entry_stat(ae)->st_nlink);
+ assertEqualInt(1, archive_entry_uid(ae));
+ assertEqualInt(2, archive_entry_gid(ae));
+ } else if (strcmp("sym2", archive_entry_pathname(ae)) == 0) {
+ /* A symlink to the regular file. */
+ assertEqualInt(AE_IFLNK, archive_entry_filetype(ae));
+ assertEqualString(path2, archive_entry_symlink(ae));
+ assertEqualInt(0, archive_entry_size(ae));
+ assertEqualInt(172802, archive_entry_mtime(ae));
+ assertEqualInt(1, archive_entry_stat(ae)->st_nlink);
+ assertEqualInt(1, archive_entry_uid(ae));
+ assertEqualInt(2, archive_entry_gid(ae));
+ } else if (strcmp("sym3", archive_entry_pathname(ae)) == 0) {
+ /* A symlink to the regular file. */
+ assertEqualInt(AE_IFLNK, archive_entry_filetype(ae));
+ assertEqualString(path3, archive_entry_symlink(ae));
+ assertEqualInt(0, archive_entry_size(ae));
+ assertEqualInt(172802, archive_entry_mtime(ae));
+ assertEqualInt(1, archive_entry_stat(ae)->st_nlink);
+ assertEqualInt(1, archive_entry_uid(ae));
+ assertEqualInt(2, archive_entry_gid(ae));
+ } else {
+ failure("Saw a file that shouldn't have been there");
+ assertEqualString(archive_entry_pathname(ae), "");
+ }
+ }
+
+ /* End of archive. */
+ assertEqualInt(ARCHIVE_EOF, archive_read_next_header(a, &ae));
+
+ /* Verify archive format. */
+ assertEqualInt(archive_compression(a), ARCHIVE_COMPRESSION_BZIP2);
+ assertEqualInt(archive_format(a), ARCHIVE_FORMAT_ISO9660_ROCKRIDGE);
+
+ /* Close the archive. */
+ assertEqualInt(0, archive_read_close(a));
+ assertEqualInt(0, archive_read_finish(a));
+}
+
+
--- /dev/null
+begin 644 test_read_format_isorr_ce.iso.bz2
+M0EIH.3%!629368!]L?(``/)__?_]B_[59___/___Z[[GGF8@JT"`)`!!@2``
+M1`/5&-`#WJ%!#5H88,4(%,TF3U,FB;331&FU&FAH:&FC-$-`80!Z0TP3TFU'
+MJ-!IIY)H-$`@GHC(-"@&FTU`]08-0T`&(P1D``!IH]!#"&@@P```````````
+M`````````"#`````````````````````"))32>DIZGBCR:GM1J'ZD:,FU-'H
+MU`T#:AY0VIIA-!D`8-31Z@8@VD>I"\<UJ[/UT[#NMYA0,2.4Z/`72'Q@T\9P
+MC4-G:]\:FKK?X6N#AN6[=N2]DJX!`EH2DP(A(I%$"-`'GH`#D1'7JCLP`=$X
+MY0`+_4!`0*R4N;E#X^8@A=$!%3(,&I$8@A(`Q@H"I=%=4!NH%:8*6M&4."/%
+MFMWMPNP5N^;;>D+BCE1"L0S.SJU*I)&X2C>:$0@D7O%QXIQ3,-`70.=$SL,(
+M.GJ:K<=^:V%HI9E"AA&8#"#V&].^X%.FHW!H1\Y306@'U3NUVLL_FU#[:FTV
+M]R@/"+E-(<TPH!8!E,1A$D)"1,4!WRT)"4864W!=F8!R*3G"'+PMATH/VS$`
+MY&SSOC\BW[B'3R[1Z%<,0R"&!3??#=G;Y=L^)&J*7",&B$(%3AJ2T)Q5JHR%
+MST4!5CUW!\/$9B(P!%/GMH?3],S3K!WIW)L[S1RFL^4S?L/N13S:^WGOV'['
+M:985&:_!*>LOD<JH*@*(QF`H,8`B$@D$%48D4\7HQ(2&ST(Y3*8I4XU73;_>
+M$,D%U$PBDD4)W4F!DH!P?4LLVG%P.S-8*.06M+Q8!IH.R=L%^%Z*]"FB6ZTF
+M@C.%%2.!5X$`P2%*A[C+&@!H`0'FLE-"O`;L#+(RR!H&J*.]>8*4NHUMT1\Q
+M:G5<?;&0[-Q&;-P?5&.+<$,G0#@@TQ6`]+<$Y7VL-QSRY2FC5:@B.*O"'T+8
+M[8MG5,6H34+S/P8\!?K/,=`=P-,(MZF(S4ULS'?8C.FZ054Y'57%9G4Q?$2'
+MK:)S8$BD`H*AC1'%0)5BF=M%@X<`0QA3$2+(OD:Y9ZV]3@)D!&&/_7\*"UCJ
+M)P<VE(^PBK:4T*4%VC,(.L&3L1-X$\HPQ4YE$8XW3X^[]0,KGWH&R'[KJ@A1
+M$*]&`P@C")4IKW*DR7+4M8CX4P-L@)+[<)I;'9"?HLQ8)@>$6'&J^YNOB;#`
+MI*89JF8QSZQAAA;^U9L[K4*UK/OH@A?+"TD,#IO7:J*2L;^$D3X#A(5#L8CR
+MV5!$>6@`:9^WWX/P99?($AO.$1=_*X3DS1K_W^5ZT1XCE`@6S'J<FQ<@.E)V
+M5";F->X<1>=H'!`:=%@#$:1M1F<7CG:)P82U.K.Y'._SOT:N]?X&WII>F7&&
+E5#?E1DF9X+6.9L;'_.!4$4QOU#OP'FFV;9_XNY(IPH2$`^V/D```
+`
+end