From f7ec974a4036cd72bbcdaeef7b737096728fba33 Mon Sep 17 00:00:00 2001 From: Michihiro NAKAJIMA Date: Mon, 23 Nov 2009 03:40:03 -0500 Subject: [PATCH] Add RPM filter. SVN-Revision: 1677 --- Makefile.am | 5 + libarchive/CMakeLists.txt | 1 + libarchive/archive.h | 2 + .../archive_read_support_compression_all.c | 2 + .../archive_read_support_compression_rpm.c | 287 ++++++++++++++++++ libarchive/test/CMakeLists.txt | 2 + .../test_read_format_cpio_svr4_bzip2_rpm.c | 127 ++++++++ ...est_read_format_cpio_svr4_bzip2_rpm.rpm.uu | 47 +++ .../test_read_format_cpio_svr4_gzip_rpm.c | 127 ++++++++ ...test_read_format_cpio_svr4_gzip_rpm.rpm.uu | 46 +++ 10 files changed, 646 insertions(+) create mode 100644 libarchive/archive_read_support_compression_rpm.c create mode 100644 libarchive/test/test_read_format_cpio_svr4_bzip2_rpm.c create mode 100644 libarchive/test/test_read_format_cpio_svr4_bzip2_rpm.rpm.uu create mode 100644 libarchive/test/test_read_format_cpio_svr4_gzip_rpm.c create mode 100644 libarchive/test/test_read_format_cpio_svr4_gzip_rpm.rpm.uu diff --git a/Makefile.am b/Makefile.am index 9f7387cf4..bdc8547df 100644 --- a/Makefile.am +++ b/Makefile.am @@ -123,6 +123,7 @@ libarchive_la_SOURCES= \ libarchive/archive_read_support_compression_gzip.c \ libarchive/archive_read_support_compression_none.c \ libarchive/archive_read_support_compression_program.c \ + libarchive/archive_read_support_compression_rpm.c \ libarchive/archive_read_support_compression_uu.c \ libarchive/archive_read_support_compression_xz.c \ libarchive/archive_read_support_format_all.c \ @@ -257,7 +258,9 @@ libarchive_test_SOURCES= \ libarchive/test/test_read_format_cpio_bin_lzma.c \ libarchive/test/test_read_format_cpio_bin_xz.c \ libarchive/test/test_read_format_cpio_odc.c \ + libarchive/test/test_read_format_cpio_svr4_bzip2_rpm.c \ libarchive/test/test_read_format_cpio_svr4_gzip.c \ + libarchive/test/test_read_format_cpio_svr4_gzip_rpm.c \ libarchive/test/test_read_format_cpio_svr4c_Z.c \ libarchive/test/test_read_format_empty.c \ libarchive/test/test_read_format_gtar_gz.c \ @@ -349,6 +352,8 @@ libarchive_test_EXTRA_DIST=\ libarchive/test/test_pax_filename_encoding.tar.uu \ libarchive/test/test_read_format_ar.ar.uu \ libarchive/test/test_read_format_cpio_bin_be.cpio.uu \ + libarchive/test/test_read_format_cpio_svr4_bzip2_rpm.rpm.uu \ + libarchive/test/test_read_format_cpio_svr4_gzip_rpm.rpm.uu \ libarchive/test/test_read_format_gtar_sparse_1_13.tar.uu \ libarchive/test/test_read_format_gtar_sparse_1_17.tar.uu \ libarchive/test/test_read_format_gtar_sparse_1_17_posix00.tar.uu \ diff --git a/libarchive/CMakeLists.txt b/libarchive/CMakeLists.txt index af7dab72b..ce3451ad2 100644 --- a/libarchive/CMakeLists.txt +++ b/libarchive/CMakeLists.txt @@ -43,6 +43,7 @@ SET(libarchive_SOURCES archive_read_support_compression_gzip.c archive_read_support_compression_none.c archive_read_support_compression_program.c + archive_read_support_compression_rpm.c archive_read_support_compression_uu.c archive_read_support_compression_xz.c archive_read_support_format_all.c diff --git a/libarchive/archive.h b/libarchive/archive.h index 6bd1b34b2..6db0242dd 100644 --- a/libarchive/archive.h +++ b/libarchive/archive.h @@ -244,6 +244,7 @@ typedef int archive_close_callback(struct archive *, void *_client_data); #define ARCHIVE_COMPRESSION_LZMA 5 #define ARCHIVE_COMPRESSION_XZ 6 #define ARCHIVE_COMPRESSION_UU 7 +#define ARCHIVE_COMPRESSION_RPM 8 /* * Codes returned by archive_format. @@ -319,6 +320,7 @@ __LA_DECL int archive_read_support_compression_program_signature (struct archive *, const char *, const void * /* match */, size_t); +__LA_DECL int archive_read_support_compression_rpm(struct archive *); __LA_DECL int archive_read_support_compression_uu(struct archive *); __LA_DECL int archive_read_support_compression_xz(struct archive *); diff --git a/libarchive/archive_read_support_compression_all.c b/libarchive/archive_read_support_compression_all.c index f4708ca81..6cff93dd3 100644 --- a/libarchive/archive_read_support_compression_all.c +++ b/libarchive/archive_read_support_compression_all.c @@ -46,6 +46,8 @@ archive_read_support_compression_all(struct archive *a) archive_read_support_compression_xz(a); /* The decode code doesn't use an outside library. */ archive_read_support_compression_uu(a); + /* The decode code doesn't use an outside library. */ + archive_read_support_compression_rpm(a); /* Note: We always return ARCHIVE_OK here, even if some of the * above return ARCHIVE_WARN. The intent here is to enable diff --git a/libarchive/archive_read_support_compression_rpm.c b/libarchive/archive_read_support_compression_rpm.c new file mode 100644 index 000000000..57e6e782a --- /dev/null +++ b/libarchive/archive_read_support_compression_rpm.c @@ -0,0 +1,287 @@ +/*- + * 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 "archive_platform.h" + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif + +#include "archive.h" +#include "archive_endian.h" +#include "archive_private.h" +#include "archive_read_private.h" + +struct rpm { + int64_t total_in; + size_t hpos; + size_t hlen; + unsigned char header[16]; + enum { + ST_LEAD, /* Skipping 'Lead' section. */ + ST_HEADER, /* Reading 'Header' section; + * first 16 bytes. */ + ST_HEADER_DATA, /* Skipping 'Header' section. */ + ST_PADDING, /* Skipping padding data after the + * 'Header' section. */ + ST_ARCHIVE /* Reading 'Archive' section. */ + } state; + int first_header; +}; +#define RPM_LEAD_SIZE 96 /* Size of 'Lead' section. */ + +static int rpm_bidder_bid(struct archive_read_filter_bidder *, + struct archive_read_filter *); +static int rpm_bidder_init(struct archive_read_filter *); + +static ssize_t rpm_filter_read(struct archive_read_filter *, + const void **); +static int rpm_filter_close(struct archive_read_filter *); + +int +archive_read_support_compression_rpm(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter_bidder *bidder; + + bidder = __archive_read_get_bidder(a); + archive_clear_error(_a); + if (bidder == NULL) + return (ARCHIVE_FATAL); + + bidder->data = NULL; + bidder->bid = rpm_bidder_bid; + bidder->init = rpm_bidder_init; + bidder->options = NULL; + bidder->free = NULL; + return (ARCHIVE_OK); +} + +static int +rpm_bidder_bid(struct archive_read_filter_bidder *self, + struct archive_read_filter *filter) +{ + const unsigned char *b; + ssize_t avail; + int bits_checked; + + (void)self; /* UNUSED */ + + b = __archive_read_filter_ahead(filter, 8, &avail); + if (b == NULL) + return (0); + + bits_checked = 0; + /* + * Verify Header Magic Bytes : 0xed 0xab 0xee 0xdb + */ + if (b[0] != 0xed) + return (0); + bits_checked += 8; + if (b[1] != 0xab) + return (0); + bits_checked += 8; + if (b[2] != 0xee) + return (0); + bits_checked += 8; + if (b[3] != 0xdb) + return (0); + bits_checked += 8; + /* + * Check major version. + */ + if (b[4] != 3 && b[4] != 4) + return (0); + bits_checked += 8; + /* + * Check package type; binary or source. + */ + if (b[6] != 0) + return (0); + bits_checked += 8; + if (b[7] != 0 && b[7] != 1) + return (0); + bits_checked += 8; + + return (bits_checked); +} + +static int +rpm_bidder_init(struct archive_read_filter *self) +{ + struct rpm *rpm; + + self->code = ARCHIVE_COMPRESSION_RPM; + self->name = "rpm"; + self->read = rpm_filter_read; + self->skip = NULL; /* not supported */ + self->close = rpm_filter_close; + + rpm = (struct rpm *)calloc(sizeof(*rpm), 1); + if (rpm == NULL) { + archive_set_error(&self->archive->archive, ENOMEM, + "Can't allocate data for rpm"); + return (ARCHIVE_FATAL); + } + + self->data = rpm; + rpm->state = ST_LEAD; + + return (ARCHIVE_OK); +} + +static ssize_t +rpm_filter_read(struct archive_read_filter *self, const void **buff) +{ + struct rpm *rpm; + const unsigned char *b; + ssize_t avail_in, total; + size_t used, n; + uint32_t section; + uint32_t bytes; + + rpm = (struct rpm *)self->data; + *buff = NULL; + total = avail_in = 0; + b = NULL; + used = 0; + do { + if (b == NULL) { + b = __archive_read_filter_ahead(self->upstream, 1, + &avail_in); + if (b == NULL) { + if (avail_in < 0) + return (ARCHIVE_FATAL); + else + break; + } + } + + switch (rpm->state) { + case ST_LEAD: + if (rpm->total_in + avail_in < RPM_LEAD_SIZE) + used += avail_in; + else { + n = RPM_LEAD_SIZE - rpm->total_in; + used += n; + b += n; + rpm->state = ST_HEADER; + rpm->hpos = 0; + rpm->hlen = 0; + rpm->first_header = 1; + } + break; + case ST_HEADER: + n = 16 - rpm->hpos; + if (n > avail_in - used) + n = avail_in - used; + memcpy(rpm->header+rpm->hpos, b, n); + b += n; + used += n; + rpm->hpos += n; + + if (rpm->hpos == 16) { + if (rpm->header[0] != 0x8e || + rpm->header[1] != 0xad || + rpm->header[2] != 0xe8 || + rpm->header[3] != 0x01) { + if (rpm->first_header) { + archive_set_error( + &self->archive->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Unrecoginized rpm header"); + return (ARCHIVE_FATAL); + } + rpm->state = ST_ARCHIVE; + *buff = rpm->header; + total = rpm->hpos; + break; + } + /* Calculate 'Header' length. */ + section = archive_be32dec(rpm->header+8); + bytes = archive_be32dec(rpm->header+12); + rpm->hlen = 16 + section * 16 + bytes; + rpm->state = ST_HEADER_DATA; + rpm->first_header = 0; + } + break; + case ST_HEADER_DATA: + n = rpm->hlen - rpm->hpos; + if (n > avail_in - used) + n = avail_in - used; + b += n; + used += n; + rpm->hpos += n; + if (rpm->hpos == rpm->hlen) + rpm->state = ST_PADDING; + break; + case ST_PADDING: + while (used < (size_t)avail_in) { + if (*b != 0) { + /* Read next header. */ + rpm->state = ST_HEADER; + rpm->hpos = 0; + rpm->hlen = 0; + break; + } + b++; + used++; + } + break; + case ST_ARCHIVE: + *buff = b; + total = avail_in; + used = avail_in; + break; + } + if (used == avail_in) { + rpm->total_in += used; + __archive_read_filter_consume(self->upstream, used); + b = NULL; + used = 0; + } + } while (total == 0 && avail_in > 0); + + if (used > 0 && b != NULL) { + rpm->total_in += used; + __archive_read_filter_consume(self->upstream, used); + } + return (total); +} + +static int +rpm_filter_close(struct archive_read_filter *self) +{ + struct rpm *rpm; + + rpm = (struct rpm *)self->data; + free(rpm); + + return (ARCHIVE_OK); +} + diff --git a/libarchive/test/CMakeLists.txt b/libarchive/test/CMakeLists.txt index 69f494ccb..86afba358 100644 --- a/libarchive/test/CMakeLists.txt +++ b/libarchive/test/CMakeLists.txt @@ -51,7 +51,9 @@ IF(ENABLE_TEST) test_read_format_cpio_bin_lzma.c test_read_format_cpio_bin_xz.c test_read_format_cpio_odc.c + test_read_format_cpio_svr4_bzip2_rpm.c test_read_format_cpio_svr4_gzip.c + test_read_format_cpio_svr4_gzip_rpm.c test_read_format_cpio_svr4c_Z.c test_read_format_empty.c test_read_format_gtar_gz.c diff --git a/libarchive/test/test_read_format_cpio_svr4_bzip2_rpm.c b/libarchive/test/test_read_format_cpio_svr4_bzip2_rpm.c new file mode 100644 index 000000000..3b4170cb7 --- /dev/null +++ b/libarchive/test/test_read_format_cpio_svr4_bzip2_rpm.c @@ -0,0 +1,127 @@ +/*- + * 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_cpio_svr4_bzip2_rpm.c | /bin/sh + +F=test_read_format_cpio_svr4_bzip2_rpm.rpm +NAME=rpmsample +TMPRPM=/tmp/rpm +rm -rf ${TMPRPM} +mkdir -p ${TMPRPM}/BUILD +mkdir -p ${TMPRPM}/RPMS +mkdir -p ${TMPRPM}/SOURCES +mkdir -p ${TMPRPM}/SPECS +mkdir -p ${TMPRPM}/SRPMS +echo "hello" > ${TMPRPM}/BUILD/file1 +echo "hello" > ${TMPRPM}/BUILD/file2 +echo "hello" > ${TMPRPM}/BUILD/file3 +cat > ${TMPRPM}/SPECS/${NAME}.spec < ${F}.uu + +rm -rf ${TMPRPM} +exit 1 +*/ + +DEFINE_TEST(test_read_format_cpio_svr4_bzip2_rpm) +{ + struct archive_entry *ae; + struct archive *a; + const char *name = "test_read_format_cpio_svr4_bzip2_rpm.rpm"; + int r; + + 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(ARCHIVE_OK, archive_read_finish(a)); + return; + } + assertEqualIntA(a, ARCHIVE_OK, r); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_support_compression_rpm(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); + extract_reference_file(name); + assertEqualIntA(a, ARCHIVE_OK, archive_read_open_filename(a, name, 2)); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("./etc/file1", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("./etc/file2", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("./etc/file3", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + + /* Verify the end-of-archive. */ + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + + /* Verify that the format detection worked. */ + assertEqualInt(archive_compression(a), ARCHIVE_COMPRESSION_BZIP2); + assertEqualString(archive_compression_name(a), "bzip2"); + assertEqualInt(archive_format(a), ARCHIVE_FORMAT_CPIO_SVR4_NOCRC); + + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); + assertEqualInt(ARCHIVE_OK, archive_read_finish(a)); +} + diff --git a/libarchive/test/test_read_format_cpio_svr4_bzip2_rpm.rpm.uu b/libarchive/test/test_read_format_cpio_svr4_bzip2_rpm.rpm.uu new file mode 100644 index 000000000..884ca9487 --- /dev/null +++ b/libarchive/test/test_read_format_cpio_svr4_bzip2_rpm.rpm.uu @@ -0,0 +1,47 @@ +begin 644 test_read_format_cpio_svr4_bzip2_rpm.rpm +M[:ONVP,``````7)P;7-A;7!L92TQ+C`N,"TQ```````````````````````` +M```````````````````````````````````````````!``4````````````` +M````````CJWH`0`````````%````5````#X````'````1````!````$-```` +M!@`````````!```#Z`````0````L`````0```^P````'````,````!````/O +M````!````$`````!,F4X-3)F-39E,#,W83EE.61A9C`````$```1F````!@```GX````!```$;``` +M``8```*``````0``!'0````$```"E`````,```1U````!````J`````#```$ +M=@````@```*L`````P``!'<````$```"Q`````,```1X````!````M`````# +M0P!R<&US86UP;&4`,2XP+C``,0!386UP;&4@9&%T82!O9B!24$T@9FEL=&5R +M(&]F(&QI8F%R8VAI=F4`4V%M<&QE(&1A=&$N`````$L)MWQC=64M9&5S:W1O +M<``````20E-$`%5NFEP,@`Y`&YO87)C:"UR<&TM;&EN=7@````````````````````````` +M`0````$````!`$%30TE)('1E>'0`9&ER96-T;W)Y```````````````````` +M````````````````````/P````?___SP````$$)::#DQ05DF4UFX89DX``!= +M?X!,$`@`*`'_X"(D%``[9(0`(`"2B/50T#0#0`](9!%(C1,0&GJ`R`M@PQRG +M ${TMPRPM}/BUILD/file1 +echo "hello" > ${TMPRPM}/BUILD/file2 +echo "hello" > ${TMPRPM}/BUILD/file3 +cat > ${TMPRPM}/SPECS/${NAME}.spec < ${F}.uu + +rm -rf ${TMPRPM} +exit 1 +*/ + +DEFINE_TEST(test_read_format_cpio_svr4_gzip_rpm) +{ + struct archive_entry *ae; + struct archive *a; + const char *name = "test_read_format_cpio_svr4_gzip_rpm.rpm"; + int r; + + assert((a = archive_read_new()) != NULL); + r = archive_read_support_compression_gzip(a); + if (r == ARCHIVE_WARN) { + skipping("gzip reading not fully supported on this platform"); + assertEqualInt(ARCHIVE_OK, archive_read_finish(a)); + return; + } + assertEqualIntA(a, ARCHIVE_OK, r); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_support_compression_rpm(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_support_format_all(a)); + extract_reference_file(name); + assertEqualIntA(a, ARCHIVE_OK, archive_read_open_filename(a, name, 2)); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("./etc/file1", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("./etc/file2", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString("./etc/file3", archive_entry_pathname(ae)); + assertEqualInt(86401, archive_entry_mtime(ae)); + + /* Verify the end-of-archive. */ + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + + /* Verify that the format detection worked. */ + assertEqualInt(archive_compression(a), ARCHIVE_COMPRESSION_GZIP); + assertEqualString(archive_compression_name(a), "gzip"); + assertEqualInt(archive_format(a), ARCHIVE_FORMAT_CPIO_SVR4_NOCRC); + + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); + assertEqualInt(ARCHIVE_OK, archive_read_finish(a)); +} + diff --git a/libarchive/test/test_read_format_cpio_svr4_gzip_rpm.rpm.uu b/libarchive/test/test_read_format_cpio_svr4_gzip_rpm.rpm.uu new file mode 100644 index 000000000..54d305b77 --- /dev/null +++ b/libarchive/test/test_read_format_cpio_svr4_gzip_rpm.rpm.uu @@ -0,0 +1,46 @@ +begin 644 test_read_format_cpio_svr4_gzip_rpm.rpm +M[:ONVP,``````7)P;7-A;7!L92TQ+C`N,"TQ```````````````````````` +M```````````````````````````````````````````!``4````````````` +M````````CJWH`0`````````%````5````#X````'````1````!````$-```` +M!@`````````!```#Z`````0````L`````0```^P````'````,````!````/O +M````!````$`````!9&9E8V,W,#4T,C@X,V1D-&8P-6%E.#4Y,S6QO861&:6QE +MFEP`#D`;F]A``````````````` +M```````!`````0````$`05-#24D@=&5X=`!D:7)E8W1O@$!@"1O'?9"`(````` +` +end -- 2.47.3