From 3d0062ba61adab5d5d67d9fae93ad43bcab8ed4d Mon Sep 17 00:00:00 2001 From: Fedele Mantuano Date: Thu, 24 Oct 2024 08:24:11 +0200 Subject: [PATCH 1/8] Improved importing --- README.md | 2 +- src/mailparser/__init__.py | 14 ++++++++++++-- tests/test_mail_parser.py | 13 ++++++------- tests/test_main.py | 9 +-------- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 06345f8..ddee83c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![PyPI version](https://badge.fury.io/py/mail-parser.svg)](https://badge.fury.io/py/mail-parser) +![PyPI - Version](https://img.shields.io/pypi/v/mail-parser) [![Coverage Status](https://coveralls.io/repos/github/SpamScope/mail-parser/badge.svg?branch=develop)](https://coveralls.io/github/SpamScope/mail-parser?branch=develop) ![SpamScope](https://raw.githubusercontent.com/SpamScope/spamscope/develop/docs/logo/spamscope.png) diff --git a/src/mailparser/__init__.py b/src/mailparser/__init__.py index cc25a85..0d3c73e 100644 --- a/src/mailparser/__init__.py +++ b/src/mailparser/__init__.py @@ -18,7 +18,7 @@ """ -from .mailparser import ( +from mailparser.mailparser import ( MailParser, parse_from_bytes, parse_from_file, @@ -26,4 +26,14 @@ parse_from_file_obj, parse_from_string) -from .utils import get_header +from mailparser.utils import get_header + +__all__ = [ + "MailParser", + "parse_from_bytes", + "parse_from_file", + "parse_from_file_msg", + "parse_from_file_obj", + "parse_from_string", + "get_header" +] \ No newline at end of file diff --git a/tests/test_mail_parser.py b/tests/test_mail_parser.py index 143b3f7..fde9c23 100644 --- a/tests/test_mail_parser.py +++ b/tests/test_mail_parser.py @@ -19,7 +19,6 @@ import datetime import hashlib -import logging import os import shutil import six @@ -27,11 +26,6 @@ import tempfile import unittest -base_path = os.path.realpath(os.path.dirname(__file__)) -root = os.path.join(base_path, '..') -sys.path.append(root) - -logging.getLogger().addHandler(logging.NullHandler()) import mailparser from mailparser.utils import ( @@ -47,9 +41,14 @@ parse_received, random_string, ) - from mailparser.exceptions import MailParserEnvironmentError + +base_path = os.path.realpath(os.path.dirname(__file__)) +root = os.path.join(base_path, '..') + + + mail_test_1 = os.path.join(base_path, 'mails', 'mail_test_1') mail_test_2 = os.path.join(base_path, 'mails', 'mail_test_2') mail_test_3 = os.path.join(base_path, 'mails', 'mail_test_3') diff --git a/tests/test_main.py b/tests/test_main.py index 7a86a8c..f9ea474 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -17,16 +17,9 @@ limitations under the License. """ -import logging -import os -import sys -import unittest -base_path = os.path.realpath(os.path.dirname(__file__)) -root = os.path.join(base_path, '..') -sys.path.append(root) +import unittest -logging.getLogger().addHandler(logging.NullHandler()) from mailparser.__main__ import get_args From 2bc4a154e32dfaf6504884efa277ea687213d215 Mon Sep 17 00:00:00 2001 From: Fedele Mantuano Date: Thu, 24 Oct 2024 08:35:52 +0200 Subject: [PATCH 2/8] Fixed coverage in GitHub actions --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4822831..cbbceef 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,7 +33,7 @@ jobs: - name: Run tests run: | - pytest + pytest --cov=mailparser --cov-report=xml python -m mailparser -v python -m mailparser -h mail-parser -f tests/mails/mail_malformed_3 -j @@ -57,4 +57,4 @@ jobs: name: build-artifacts path: | dist/mail-parser-*.tar.gz - dist/mail_parser-*.whl \ No newline at end of file + dist/mail_parser-*.whl From 5ece437c62e13917fb64bdbf17cc868638154f2e Mon Sep 17 00:00:00 2001 From: Fedele Mantuano Date: Thu, 24 Oct 2024 08:39:32 +0200 Subject: [PATCH 3/8] Fixed coverage in GitHub actions --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cbbceef..8641c46 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,7 +33,7 @@ jobs: - name: Run tests run: | - pytest --cov=mailparser --cov-report=xml + pytest --cov=src/mailparser --cov-report=xml python -m mailparser -v python -m mailparser -h mail-parser -f tests/mails/mail_malformed_3 -j From bf8f6b3cc374e9817d649af7e6aa52891bd7090c Mon Sep 17 00:00:00 2001 From: Fedele Mantuano Date: Thu, 24 Oct 2024 08:45:36 +0200 Subject: [PATCH 4/8] Fixed coverage --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8641c46..c661ea8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,8 +32,10 @@ jobs: sudo cpan -f -i Email::Outlook::Message - name: Run tests + env: + PYTHONPATH: src run: | - pytest --cov=src/mailparser --cov-report=xml + pytest --cov=mailparser --cov-report=xml python -m mailparser -v python -m mailparser -h mail-parser -f tests/mails/mail_malformed_3 -j From 279a1ddb3862444df30d27542c67f5da410b6d7b Mon Sep 17 00:00:00 2001 From: Fedele Mantuano Date: Thu, 24 Oct 2024 08:51:41 +0200 Subject: [PATCH 5/8] Adding pre commit --- .pre-commit-config.yaml | 29 ++++++ Makefile | 2 +- README.md | 2 +- docker/Dockerfile | 2 +- docker/README.md | 2 +- pyproject.toml | 2 +- setup.cfg | 2 +- setup.py | 2 +- src/mailparser/__init__.py | 2 +- tests/mails/mail_malformed_1 | 2 +- tests/mails/mail_malformed_2 | 3 +- tests/mails/mail_malformed_3 | 1 - tests/mails/mail_test_1 | 1 - tests/mails/mail_test_10 | 3 +- tests/mails/mail_test_11 | 1 - tests/mails/mail_test_13 | 2 +- tests/mails/mail_test_14 | 2 +- tests/mails/mail_test_15 | 187 +++++++++++++++++------------------ tests/mails/mail_test_2 | 1 - tests/mails/mail_test_5 | 10 +- tests/mails/mail_test_6 | 4 +- tests/mails/mail_test_7 | 5 +- tests/mails/mail_test_8 | 29 +++--- tests/mails/mail_test_9 | 8 +- tests/test_mail_parser.py | 9 -- tests/test_main.py | 4 - 26 files changed, 161 insertions(+), 156 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..27a9b84 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + args: ['--maxkb=5000'] + - id: check-case-conflict + - id: check-json + - id: check-merge-conflict + - id: detect-aws-credentials + args: ["--allow-missing-credentials"] + - id: detect-private-key + - id: mixed-line-ending + - id: check-ast + +#- repo: https://github.com/astral-sh/ruff-pre-commit +# # Ruff version. +# rev: v0.3.2 +# hooks: +# # Run the linter. +# - id: ruff +# args: [ --fix ] +# # Run the formatter. +# - id: ruff-format \ No newline at end of file diff --git a/Makefile b/Makefile index af91c2b..b35567c 100644 --- a/Makefile +++ b/Makefile @@ -55,4 +55,4 @@ dist: clean-all ## builds source and wheel package python -m build release: dist ## package and upload a release - twine upload dist/* \ No newline at end of file + twine upload dist/* diff --git a/README.md b/README.md index ddee83c..149c597 100644 --- a/README.md +++ b/README.md @@ -288,4 +288,4 @@ Then you can try to run the command line tool: $ mail-parser -f tests/mails/mail_malformed_3 -j ``` -If all is ok, you can start to develop. \ No newline at end of file +If all is ok, you can start to develop. diff --git a/docker/Dockerfile b/docker/Dockerfile index d6cd82a..fe88d45 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ RUN apt-get -yqq update; \ apt-get -yqq --no-install-recommends install libemail-outlook-message-perl; \ apt-get clean; \ rm -rf /var/lib/apt/lists/*; \ - git clone -b $BRANCH --single-branch https://github.com/SpamScope/mail-parser.git $MAIL_PARSER_PATH; \ + git clone -b $BRANCH --single-branch https://github.com/SpamScope/mail-parser.git $MAIL_PARSER_PATH; \ cd $MAIL_PARSER_PATH && python setup.py install ENTRYPOINT ["mailparser"] CMD ["-h"] diff --git a/docker/README.md b/docker/README.md index f21ee6c..48e0ebe 100644 --- a/docker/README.md +++ b/docker/README.md @@ -17,7 +17,7 @@ This command runs mail-parser help as default, but you can use all others option To share the "mails" directory between your host and the container, create a "mails" directory on your host. -There also is an example of `docker-compose` +There also is an example of `docker-compose` From the `docker-compose.yml` directory, run: diff --git a/pyproject.toml b/pyproject.toml index d984fdc..121a39f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [build-system] requires = ["setuptools >= 40.6.0", "wheel"] -build-backend = "setuptools.build_meta" \ No newline at end of file +build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg index ca0c341..c068325 100644 --- a/setup.cfg +++ b/setup.cfg @@ -67,4 +67,4 @@ addopts = --cov-report=html --junitxml=junit.xml --verbose -testpaths = tests \ No newline at end of file +testpaths = tests diff --git a/setup.py b/setup.py index f44181b..56c1053 100644 --- a/setup.py +++ b/setup.py @@ -17,4 +17,4 @@ import setuptools if __name__ == "__main__": - setuptools.setup() \ No newline at end of file + setuptools.setup() diff --git a/src/mailparser/__init__.py b/src/mailparser/__init__.py index 0d3c73e..3be35ec 100644 --- a/src/mailparser/__init__.py +++ b/src/mailparser/__init__.py @@ -36,4 +36,4 @@ "parse_from_file_obj", "parse_from_string", "get_header" -] \ No newline at end of file +] diff --git a/tests/mails/mail_malformed_1 b/tests/mails/mail_malformed_1 index 233beee..facb7c6 100644 --- a/tests/mails/mail_malformed_1 +++ b/tests/mails/mail_malformed_1 @@ -1655,6 +1655,6 @@ aC/aSJ9oUyPWj55G+u04SLqWt5JYPm8zi6y5cdAJlGxIY5V59fwaXRM+5L7sSCWU5F12PFPV nWhFxo5oBxXfl4a11T1lpCMm/iWZODQLdb1vIvu3OPKliaxvZNzKHL+56sLd5eU9IuP/AFBL AQI/ABQAAAAIAPNNuEio5rjVvlIBAACkAgAcACQAAAAAAAAAIAAAAAAAAAAyMDE2MDUyM18y MTE0MzkuanBnXy5qcGcuZXhlCgAgAAAAAAABABgASRlyipC10QGWFpjdkLXRAZYWmN2QtdEB -UEsFBgAAAAABAAEAbgAAAPhSAQAAAA== +UEsFBgAAAAABAAEAbgAAAPhSAQAAAA== ------=Part_0118260_79300441.9934604411926-- diff --git a/tests/mails/mail_malformed_2 b/tests/mails/mail_malformed_2 index fdef74b..7c8b6b2 100644 --- a/tests/mails/mail_malformed_2 +++ b/tests/mails/mail_malformed_2 @@ -47,7 +47,7 @@ Received: from [117.201.229.175] (unknown [117.201.229.175]) for ; Mon, 22 Aug 2016 09:22:04 +0000 (UTC) From: "Reynaldo Stevens" To: -Subject: Trabajo perfecto a distancia +Subject: Trabajo perfecto a distancia Date: 22 Aug 2016 18:37:49 +0400 Message-ID: <004801d1fc84$01c934ac$b247f0b7$@infospacemail.com> MIME-Version: 1.0 @@ -58,4 +58,3 @@ Thread-Index: Ac1doyjrxh5tdsxl1doyjrxh5tdsxl== Content-Language: en --48461/50/1471857816/MailSite/apus.netpar.com.br-- - diff --git a/tests/mails/mail_malformed_3 b/tests/mails/mail_malformed_3 index e29db40..b6413c7 100644 --- a/tests/mails/mail_malformed_3 +++ b/tests/mails/mail_malformed_3 @@ -343,4 +343,3 @@ IGRhcnNlIGRlIGJhamE8L2E+PC9zcGFuPjwvc3Ryb25nPjwvc3Bhbj48L2gxPjwvYm9keT48 L2h0bWw+ ----=_1wyyTH1wPm-- - diff --git a/tests/mails/mail_test_1 b/tests/mails/mail_test_1 index b0a1b09..d2ae7e9 100644 --- a/tests/mails/mail_test_1 +++ b/tests/mails/mail_test_1 @@ -856,4 +856,3 @@ C6bNq2LCjnZIBHBhDObnQ8vWs7GX0/X+1uFCAHOsUeK+mRVlwMAxIP7/kZD/W3+CAP9PQERRBgr6 fyjYf83330Ql+9/2fwBQSwECFAMUAAAACAC7ZhZJT+TXP2QiAABjIgAADwAAAAAAAAAAAAAAtoEA AAAA4avjpqGglI2ROTQuemlwUEsFBgAAAAABAAEAPQAAAJEiAAAAAA== --2NqJR3m2cLnhEraiqXA4Q9hqnmihx7b7 - diff --git a/tests/mails/mail_test_10 b/tests/mails/mail_test_10 index 84418dd..824fdd1 100644 --- a/tests/mails/mail_test_10 +++ b/tests/mails/mail_test_10 @@ -47,7 +47,7 @@ Received: from us1a3-smtp02.a3.dal06.isc4sb.com (10.106.154.159) Received: from us1a3-mail113.a3.dal06.isc4sb.com ([10.146.45.236]) by us1a3-smtp02.a3.dal06.isc4sb.com with ESMTP id 2017030816455096-411668 ; - Wed, 8 Mar 2017 16:45:50 +0000 + Wed, 8 Mar 2017 16:45:50 +0000 X-Disclaimed: 23094 MIME-Version: 1.0 Subject: *** ATTENZIONE *** - Modelli POWER7+ inclusi nella campagna Move To Eight @@ -4184,4 +4184,3 @@ Mzc5ODYKJSVFT0YK --=_mixed 005C1243C12580DD_=-- - diff --git a/tests/mails/mail_test_11 b/tests/mails/mail_test_11 index 0ca51c2..154ab8f 100644 --- a/tests/mails/mail_test_11 +++ b/tests/mails/mail_test_11 @@ -854,4 +854,3 @@ C6bNq2LCjnZIBHBhDObnQ8vWs7GX0/X+1uFCAHOsUeK+mRVlwMAxIP7/kZD/W3+CAP9PQERRBgr6 fyjYf83330Ql+9/2fwBQSwECFAMUAAAACAC7ZhZJT+TXP2QiAABjIgAADwAAAAAAAAAAAAAAtoEA AAAA4avjpqGglI2ROTQuemlwUEsFBgAAAAABAAEAPQAAAJEiAAAAAA== --2NqJR3m2cLnhEraiqXA4Q9hqnmihx7b7 - diff --git a/tests/mails/mail_test_13 b/tests/mails/mail_test_13 index 1bf9dd3..0997d6d 100644 --- a/tests/mails/mail_test_13 +++ b/tests/mails/mail_test_13 @@ -1418,4 +1418,4 @@ erence.us14.list-manage.com/unsubscribe?u=3D3f21937f0f636c8cec1db30bf&id= open.php?u=3D3f21937f0f636c8cec1db30bf&id=3D199bb58d0b&e=3D042ea43672" he= ight=3D"1" width=3D"1"> ---_----------=_MCPart_1336256601-- \ No newline at end of file +--_----------=_MCPart_1336256601-- diff --git a/tests/mails/mail_test_14 b/tests/mails/mail_test_14 index 319a269..c319a0a 100644 --- a/tests/mails/mail_test_14 +++ b/tests/mails/mail_test_14 @@ -30,4 +30,4 @@ Content-Transfer-Encoding: 7bit Content-Disposition: inline Plaintext here. ---===============8544575414772382491==-- \ No newline at end of file +--===============8544575414772382491==-- diff --git a/tests/mails/mail_test_15 b/tests/mails/mail_test_15 index be00535..9857668 100644 --- a/tests/mails/mail_test_15 +++ b/tests/mails/mail_test_15 @@ -118,7 +118,7 @@ MF@WO%B8RR39IT=6!#EH5LU.B*\]WG9B# MOD0?C7DM1.SEF$,C:@DI1(3J%VE&FD]SLVB4X\?%+'HW0>_93-G E'6@]*Y5 MA)HNDQ$-FH[N6)VL$S1XU,LS;1*T9;*@W$E-1SU%DL=K_3]I:"R?YDQZ3X$& MHQG.3V6U4*WR2YE=CZ_]G:1E?SJ1(]@_+1BS6*\!?V-.W$= -MFU%F$]N*FV0F[1?@E!%F#)38\H;0F$*.@3@X4FAX4$W^/"P@@J@X%@ +MFU%F$]N*FV0F[1?@E!%F#)38\H;0F$*.@3@X4FAX4$W^/"P@@J@X%@ M%F!^#B@_<":'APM%)3Q4@M9BA%F[4/%I]]Z#T"6D0U!Q0)T<3Q8)SD6L$5(X MM=N$_5/H3N$Q?Q1UW*#]!NW5?C-WKO2;H+U>%P$,+K2RK;Z'QU6B%I0N%.P" M;"O<5ZCGC4)9MO#+&OIO@O[('LOF?&92L'YT,X\1Y@%.XY'):7,Y25OI=S=$73$J! ?! M\%U@58G;=:*?_'8^U@^$'_=,J7 AU*6MO>,NQ@Z,,SU.HQ&0=N"X#4FQZ0)\ M%K\F]*?!T'X18F.;A^T7"C9WSRHWQ-0=$FJKD)>?!@N\0)B$S^GV Z;;"M:_ -MH89 ?0?5N[K>*/T7U8]2.AMW7*D"Y2D7H(4[MQTH;P(V5?-Y4&C^9_LD3O* +MH89 ?0?5N[K>*/T7U8]2.AMW7*D"Y2D7H(4[MQTH;P(V5?-Y4&C^9_LD3O* M_.#^.X1:'Z12H_PY7*A!^%"N@R28/RZ,R()NF!?DB;I93D$LZNVL#C0=9YEF M&6>U]K![A?2O&=^ULB^'6/]B9"]_G [W,? BJ0&(KRE[Y?J!G1=_4' M))P8.EG%Z83/:5]U*/G^>-]]U;&T3\DM3W/B1-,'\.*3!JF>_(1>%>C@^0$) @@ -230,7 +230,7 @@ M3\_V13XYMO\'AFE\')5H7HT9MM;\ #UJ!6]L$AY+ -M@M.$Q+M2U]\4(8WEY$G[LW]G&&N[4B*D[P)BU%A(DHM$29N5^IDIB KKL#1AC-=I1!+#(P6MF^L-T8NT.?EPB.(! M;M+NY*.HZ/&&,<_. I!K(?:4T\CGZ2UM[KQX\HL:5*?-<,"RR%\%LF8,6VXOQJ*%Z"CO;A.V G?&WGRKSR8Q@2309<("A_@)+ M'!I(\(')=VY.!!Z\CC$-MFJ&_?O".7%'*@8*8YB0;?EQ1C.H\B8(QYOWQ75\ MJG"4LX+HW.VU9O/W&,N^(A#+T.>Z5 W;+:C#,G?YZ> G9S:QU[?SXE-H=M:0 -M.2WW[1H_IL'7$H+<+K/Y&3RO/V>L *N5QA.S\UG,/DBA-Y'3SPA@32.SXC1 +M.2WW[1H_IL'7$H+<+K/Y&3RO/V>L *N5QA.S\UG,/DBA-Y'3SPA@32.SXC1 MNN!&.;#2&"\?R6#;-J.L,=Y+!E]RM=/-GV(L>T)+;^(. N-.QL204"BDK?HO M 3A"L-UN!M<:L*Q#?Q0S")7#TSJ'7+"=*,Q@!SKV:?135!3/:8H\4^2K/@;3 M0D>^S1MC6[1BWO^MI@FN0U1;STF83O-N6_"Q6$XSR9S2IC=1(+;K+Q&O<5,L @@ -335,13 +335,13 @@ M-,SZ25QO9UG:V&[$[J-Z(^,&KY@]!L24J?IHXU^B]MZD"\0:_ C!LK"CJ$6( M\D\/^_,[*>O)'B*4?PF2-F);P6#U6@I]5#GX;YU5QAJ0$A]5\O< 5BJ^LF8F M7;\M!8L5V7K.F-GZ'?1^V:(]G\3?2Z><4_\QFHQ%HPP;\.,>QS;/V-PTNHSJ MJZ/ORW1V&DO[&3?GC?OI_10JIY1ICA#3$[M,F-2:ACKPZ@,;F2;)*[/\###X -M6K"JH_F@"Y+XNMK3UD]T6'78Q?O;ELHC^!?#T/?*T5^'TX@A9KL >^@&C:T_ZXEFN.=\)EH&0"5 3UZS+X;_=N8X'H?,;X +V/ZC8-+YRE MFR]0VV>LX'G=?KQH/ZZ;]'J6!-KX8=1>,:L(?* .3E\ZPY.KE]&9\X4SF>P. M(@N#J_?XK+)/SQJ'*> -?'YWD#CH/\]L/@0M;#Y OY6QZ]>+C;B^2Z^,@,SF MYT,B(3";]Q@P"*H5]O(^Q-SSN@<5C_K)C976N<*47.4AV5ZTU\Q-@J5H?P.U MLWS\HZZUVF3H5&,>DZ%0[O?>&]D,87\#.FL&U&(%^1V:>]4@3-O3H\\I+>.0 -MP?\4RC9KS6L,97Q_C9QK4T.]E7XF_A7#>M)$OUV9Z.?2W[XI=DB#?=%AB^\ +MP?\4RC9KS6L,97Q_C9QK4T.]E7XF_A7#>M)$OUV9Z.?2W[XI=DB#?=%AB^\ MCJ(7-F!-,U=K#\_*E\?TLQ2)RU3BFF/$KSZ[C$L)W8&>H[%QSU4_,2ZM,)2>RZ81/LG6>!H: @@ -441,7 +441,7 @@ MG- $NQ)!PAU7(5[#@"U!VE^E'[@K2J,4@:T0)=L)H62X;D89[06F^&7*:* , M'%W)LTRL]A94A7'!2V)]]0^JPZ+VWAQ%9E)O?> M2)T5V>MT1;&CXD3_;")T;.>"K[N/FF+>0)S'J;A;]':N+$"2YT0*MRHARS.V M(UHQ61%A,]7 F!M6 R(PATXVK1\%'.9D1.BNC9*:76R#B^^Y1_:P@9- KS-. -M&OY[4)+@H75V.+^D-.ZGZ2]&FJ^CGPB73.FM@W@/4<\)XN<_?:JFR60-!A8.6"PM1E9SN MP'0A6:A& 9>A3?^#02G3%*O>J%,[K6)%/UWJ)K< 9] <]^KG_J4.R[!:/\UU MIHLIRUY&%TW4]+PTS3D[:_3T/,Q;=2QDWE5(%^SQ,C)P,NT JVA.?_0Q,\9! @@ -560,7 +560,7 @@ MDLM@.!\%AT7;B!Y%(>C]Y Y+#<51CD44-J/AYKS=GCWW 0K-[:KWYD1EQ*#@ MK(J00A1&N*1)0"@L1UCHE@N'KZ+ NS5LCE R"?'&/L'+5XBU5P;I+=&DR^ MO-X;Q%GCA75H])/:ZNLXSP):!P^2 OX=NFC?4/^P+EI1KZ.*FQ3O=DI;'#W3 -MI:W.3M[_Y(.H1H\]/HAJQL>^^ZH>,U_>K'@;T*\5;: ;E\DV/5P=,7#LK9K +MI:W.3M[_Y(.H1H\]/HAJQL>^^ZH>,U_>K'@;T*\5;: ;E\DV/5P=,7#LK9K MS:*)7 !3J59:. %AM0>+8N#8)(F-!D-S#"C2O%&XJ$PSZH$<;C0TQH"IO7N^ M/WQ^/CEC\'7*X52,YWC4 FOPA8V:"NQ5D<]$)\:JCQWT(+=<%0K7QV&S37 M^C\%23GG#ID-Q A?2F9=ZL:2#V ^T?MX_3Y.B<6J'NUW%3WO5RHY.\IV#ED_ MP$@>I"([MDWB,(F?&3=MP:Y0K@8U_TW84$FN87DU?3FM,GZYH2V;G&X+O<"C M/FR![QN5P*'-/R-\YX[_?%&K^$1'+D&31Q=Z@I^=+A)+K(F?)A)Z1KFR/U:N -M[+1!.6?)@TB&-J %1.][L0 MA8MVU"3J+1GI=EUL*6,7C6(^'7W)9LETKIW2-7?73JU[9S-TA[E;N.A4=GO4 MB.F0!Q]NR8/(0D]Y? +I"*;'A"*[%&6.%'GCS1LIB6'6^4M H0W%9708'%U^ -M9\3Z]7)G!-8T$!^S6-,>--BDI->.%V%B).;CB_6&0;N""TLJ0MV!"KUZ;[W +M9\3Z]7)G!-8T$!^S6-,>--BDI->.%V%B).;CB_6&0;N""TLJ0MV!"KUZ;[W M*N>B>VI^GVP?NY;$E.A#66R0W>[OM<^">O4 $Z $Q[?:O%!Q8R) MA/7.;IX('BW;),ZS@&NAL>[;K[*#JNELISO!PK8;=/X26 :QWJ M2B=B-=B%]X(,*R"+@#S()U)Q/Q,4%YZC$E$8<6)@\@I@\@I@\@I@\@JL35M@ @@ -730,7 +730,7 @@ M.S?W XHFE6 F%,SMF%-.5;CUSTM&7VZ;,DT9%,.^MFGD,S@$L N(QD*OS?T0 M'UG]\2-\N/KCQ_CP9V9\C<==_7T%[ '<\4FEQQL3$/\#%'NQ B/<=3=FZPB5 M&GGZFE5D,^B(+64&[!9XU^9_8$8,QP5%1;T+SS%),\24M<.D=83#,+':8SRD -MH>F)!YMOF$2G:S>WT#%9?XU;F)X_EYGV<"\]!T5GYG]D!GWSMUBK-B((I(+ +MH>F)!YMOF$2G:S>WT#%9?XU;F)X_EYGV<"\]!T5GYG]D!GWSMUBK-B((I(+ M0YK^&X1@THP2?-!A- '0.ZP9J:D!E9PC@U %S[7Z^=RRNGV3ZT9\"G*!W?4? MZ3P%]3,+E>'^?CPR/W$YP0G7?YGLM46>QF^O!9^7:!?)HW&W6PW:9/ENOL9FA[@HJ 27="JX=DK?W M(:-"&!3)_7ZX]XN@G^_OQ_?4P9J?2'#*?I$0=M$OUD^?F$9#BGZA?OK,U#.3 -M\;JEL]+[:^\7&.]J(453,+SV%WCMK\N@AX(OWG-P+P5?ON?@"@7?L5+P!<,TC0+?+S,1'E2K52%\'TZ: M_>'.9C]%>\=UK?8DS=9A<4VJE5287V5BF0U63J3P0$("!DELO[=_8SI.G&-O M*HX?'! &&Y@#]0_P-]P0]-E^7VVV0/7L:_[.ZK "[6^@_MW@;_ /H 7YZ_&" @@ -855,7 +855,7 @@ ME=. C D?JIY-XR?H.(%"KTXZN3Z[B]T5NT@I9+5EM=F=!.T(BNIM\R$+<"H/T!'L/E2 M)V<*8D3!*=J8^)RBYTY"K5>+YJG^4$=U&E],>K"RA\X1 M:N (5 DVE?FFGVO,^TA^4GZ>,V7-E="+D:(:EFMK^,U;ABIH] +M5.6)5B_<)O9@=)+&[2;%;>GVO,^TA^4GZ>,V7-E="+D:(:EFMK^,U;ABIH] M^APNR^7SC?,H4?>3K?%7:J!+>@QV.S6RQ1>C.S5"V5,?:S#PX .B&)+0[H7* M'52NC1OF= Y:ZM!WL&5( I\)O/O504>K >$;'/4VU8UOQA)(Q*@_A%]/ANLG MR_(R] ,]93PXT+*WDK-^0*D;E9Y/LE.5E99G!$B"+S[%:,D1F,;0Y^V>#IHP @@ -912,7 +912,7 @@ M^]OT&QNMGU,&[_,>K:;84$3]>); R >M/!=VBG',,(PQ2$.9NKX6.37*@QT" M ^[A1MCIB:&?] Z4#^WZ:0+D>-8KK?JG%.GUJY.]W3+GRIB/$YM+IH +M007E0T!4N**"HOSGG.K90/F^YSY_GJ?>KK?JG%.GUJY.]W3+GRIB/$YM+IH M6$GUM7XN3X^H_BKRS:-B%I;C]+C*XS?HB1!ZW;K6EBAW/"[^VJW0OU94*\R6 MB(, K(Q"1]!*8-[?U>K?-^S_4KCI@9&'U%IM #%WD"Y MV^W^PGE5OC,1%=7/T6H-\FRQ3-S\I-,T1D\ZY7#':P_E:F(KUNXD+IX8;$_? @@ -928,7 +928,7 @@ M)D J3HN'G8Y.)QPAUWQZ@DEH@P+C\0/3KO^+<'XGL0^7 05L[?<)D^IXF;R3$G1_D<_H/^0LWY,,N;]UP[(G1Z[Z. -MME"_"R&T#7>!?:=5EJZQ Q.U]" /?4T'G]V3^QV2R,!?:=5EJZQ Q.U]" /?4T'G]V3^QV2R,DQ:RWS["/; 9M\F4]?94XLQ%?O0^37DZG/4 M6 !V$2ZG>C6.0UQNGKZ\@3\ LNRQF\)W EE.V^E?0!MN /C[-S"!_RE5FT(O M_\.A_A[)\J--(_ ^V/#S!B[PY^52Q_F?!2:/H.'T@5D=K!O#<9\LUXU[.G9-(W\?>5_KCO] -M?R'73.Y8L;FQ)2P,??3XR]X=6#,1'RVP+=-Q!7"LAM]"_7*_^M/YF0O;7GN +M?R'73.Y8L;FQ)2P,??3XR]X=6#,1'RVP+=-Q!7"LAM]"_7*_^M/YF0O;7GN MF-6,S4,_:E37)S4MT#D-UZPB:)85.%?E,-&PW$45<@!0BIC5RG&3%VP;$C*A(0:*C"P8;G 7\;%"/C=9 M@/V]C^-J0FGX]"0^2^DN[ZGNQ6ZC_>E>K/%_L'%^H#E,$[?5WCCR UB?4!;- @@ -1040,7 +1040,7 @@ M%?!UU-87-=C67?@F8I>(E? GA1[8%6+]@:'>5;(RG!ACUS4X6D?P9P2.,YP4 MA6R4RN3X1(9Z&C>=VQUV9@*6P)KYZ\0"**^9[R$6 DPGVO@^P<%*N%NJ6P\X M0QX0:>R&6S\X3PIM!:";=C2@E[::FUBT>T?*+1>8.T)@;J7 W#H1Q9;H]L,5 MBUX?#3LV;WT\H"]AH+ZCHM5NA_C0]EWZ$L#7"=\@W$>XG_ X2'"PX3' +7L -M"WTSX$G"4_KI"F=G +7L',4OZML +Q%>031>U<^&.#A!Z&=""R; +M"WTSX$G"4_KI"F=G +7L',4OZML +Q%>031>U<^&.#A!Z&=""R; M,," OH4@&L,-L^T>WH#V*)Z([H8'0A]"/,(@PE# 2$'6G0]SJ^9YF:'L< MH)9U],2R4@@S/ ]"2H[G8<#.GNAY5\)NA#DDTXNP+Z88!WI^"I*#*5?B,,PU MCO)L@_@8SZ\A=R+A%-*B7-9$DM,)9U+*&">9N832J_FWR#O[Z:Q[#^4N!32Q @@ -1059,13 +1059,13 @@ M0E(:Y-8G90,V)A4!MB250FYK4CG@;(C#%5?2*,"%2=@:BY/&@-:RI.J@6)8; MV:;$LLF$[2F(RZ']8]D/%+^<@K,O/A7Q+<7#;&6UJ6; 2:D1@ VIV#N+P3<% M:M''K# E%..A:9@>26@EC",\F#8<9#Y.P_:1*4<)/R=<&O( V*Q,#]%96:D5 M:YT4TV9?1?^=_H!Y:/M7A-\0G@6,91?2T=N?TW=!_ ^*:S+B0;=?YF]05EGF -M'X C,]'FG9DL5&'C,K&L\9FXMD_,= OE; KAA*S?S)S59_T!V)B5"RDM686 +M'X C,]'FG9DL5&'C,K&L\9FXMD_,= OE; KAA*S?S)S59_T!V)B5"RDM686 M-9H2P+DD,Q_0YD]Y=DGHT/;V%,0AV25V"R.RW\)X]GLN6C"VL]&KZ]F%H5&, MY12&2G^(SP"Y(_"1A% M8]6/YW7^*G8(XG%)* -MK5X0']W9%W \X422F=(Y"+"I_O' 6XDG!-YUC +MK5X0']W9%W \X422F=(Y"+"I_O' 6XDG!-YUC M1R"=L\692S7G0'*7F<).-5=I%84@>4-*F2 MF59D?CJS=C&P>I59M \#VZPRJW8+L),JB]7N .8?+6TF:-%FL,H*B(5%HV1/ @@ -1098,7 +1098,7 @@ M8O/4$M:K5N:2E0TJ6TEL<[&S+]M4*Q^3E?TJ.TKLI(O-,ZJ5S\G*.95]3^RB MU&,E?F&Z']BR;H[Z_4]R/3])!Z9_Q*(4^GLJO$/'LX2_J[L# 7%FUG M&W0_LI,]'.7]Q';UE#:G^Y<"VZ.RI<3V]B0]W8O^IX!=4=DKQ/#3J,A>\]^N M^XFU]'*4]Q.;W4M:>8.LM*OL$V(+7237JWGF ,S;H+)D8IM[.?OIV=M1AY]8 -M]]Y21I$YEAXAY]G'6VRN9[E+ +M]]Y21I$YEAXAY]G'6VRN9[E+ M#-TE=D5E5P*V UO6UU'W2VQ_7[57R,H!E7D&(CO4UZ7T ;82?@"]JP.D9#5( M7F;75=9*C VT25[376:'!\J\ Y#W"SNFLI/$OACHL/D+.U0F\^*#2OFO[+#* M"H@=*Y->5P>= J8KEVQ"T';=KZRHW%'W7]GP8(85468J_L5T)M\8K,/1"? MD?M(&&.&_"_#\+.+6/=PPGC"[/RK[D9VDN,7DB9#2A@KAL "T!6P$CP M >P"8TRPKC#*!,SN , Q,+X$S/(0P$K0$&P<"P>L8I& U3#2!*N!W89@X^FM -M^[4L#O %AF_VWP'C3L ."/$5&'L"]G\I@/M9&N!G,!(%^YQE 7[!<@"_9'F +M^[4L#O %AF_VWP'C3L ."/$5&'L"]G\I@/M9&N!G,!(%^YQE 7[!<@"_9'F M7\&X%NQ'\$BPG\ 7P2[!^,0;[-T .>\!*'@O0!_>&]#(^P*>Y_T!+_"!@-_S M& 'J*82@O1@ &P4Y4L&!Q!V"(& -H%I6 H:(*,$S4@.?A@(+% MB%K 6%$'&"$#..HH&B"<29HFI@-FB&3!'3 /,%3, !XHVD"P# -M%&RDP"^JC0(4;+3 #]G? 2C8G>(NB(\!%*Q=S -<)Q8 KA=W SXJ%@$^)I8 +M%&RDP"^JC0(4;+3 #]G? 2C8G>(NB(\!%*Q=S -<)Q8 KA=W SXJ%@$^)I8 M/B[NQ:<"F?S> 7ZO(!Y:TI2GD+).>R -M_2 YE[T+DG/9 .Z4Y#_@-I,]C9R!]'CM+Z>= +M_2 YE[T+DG/9 .Z4Y#_@-I,]C9R!]'CM+Z>= M:RD;X':0W<^JW;(T-2'W6]JCUD1IH^NCMT;7\V8^D\_C1I$M"L4T,5LL$$O% M%^)K4>C>25?M\6^O0SY+C4^8GC7-#%H0M"OX4/"QX*^"68AO2%2(AN>);B&< M:7B5>IRL'F>IQ[OHR/@*T6X1P->)&5$*Z\X?%?=&:8!_KLJ=4.6^%->L;L"_ M5?E/XLYH+?#?5/Z'6!OM#OQ/E;LI/T0+UHU[* $Q>$Q0^L2BW0Q%YM 1M@OP$W8 < 3OODC#1G)-.[/_<0J(PI'(-L$Y M!/]T3FFQO(HV JYR7NS6M"+-S6FQ_.V_2?,UWYRVB0TVV^)8V+E"^:U3E,%7 MI(V]Q"F>U$5^Z0G/?;AVZ2$8('A"\(+@#<$75BP_" %0ET (08 A<#XT0PB% @@ -1328,7 +1328,7 @@ MQ:RES-J;6?LR:S]F[<^L YBU#[.F,6L6ZS^M'JI;V=S2O::^IK82VL*:!H7E M0,ADUO'C6(\IT\#>I'%L:&7]M)J*MD:0F 8U[ .A L(P"*D0,B#D01@'FE40 M:B"@VU,@3(70 F$:A.D09D!HA= &82:$8@@E$+I!Z ZA!X2>$'I!*(70&T(? M"'TA#((PN(KU;QM,Z6EN D:TMH*.7TA](,P!,)0 @@ -1366,7 +1366,7 @@ M\HH !H 18 *8 185%>]$,Z $(E: #6 'E +* .4 )Z ?( X0#Q "1 Q( &0 M"$@")*LHF;Z?-KV BC,8S.JD1(-9!=PC-%N<%,P\$4H.M=T!:C3!JM6Z\@W2 M3&6F(2=461AG-VDS#)EQ5(;47)B0GQA97JA0V,VY:ETT([88 M,14'[ ?K9D M4P$(5> ))A.#3!:5!AI69#!0"I.!"1$+F)&.67H+>9B7"KU/ T@ 4D Z( .0 -M"<@"R *0#8@%Y 'R >$ D!.\"( ?$ D( H@ "B!>32 D AS42A,%TZM,8 +M"<@"R *0#8@%Y 'R >$ D!.\"( ?$ D( H@ "B!>32 D AS42A,%TZM,8 MQ0 #P @P RP *\"FI@R6.(W&BMW7E"%!J0*#5%N&JAY7BL%28$@W:%Q1G6NH MFC+74R;RD+)&"1'=JC8;C: @TO0F+66AGZFEN.V$=K42]":=%A0MK5-LE!HI M#E4E&)1@4VE-I7JKV60$EJ?4-.N+]%8MZGXG!0K":G?0E+743)'52"M$)HE2 @@ -1447,7 +1447,7 @@ MU\.]"% ,, "R:LD%"5F*-F:AT'TT0AD3P RPF"F&=UQSB0Q&VS7(0F T6RQH M29:8B'3(('5FU]?[(X1@NB4]/L" M.#B7YVFBM3\V3V0G7C!2,\!XI2;9"$*>=J-J#5P&#8(#)0M72;#D5_Q_UQ/VC M+7'MCO!2H(%44$ ./9Z00$0"D +2 1F 3+23 #* '* 9 -R +DZHN:3-:ZC -M]9IE8R$N$"]/1Y%S!/KEA1#:R$U&U]>N5S,1FHU!WI ",EBVJS$YZJWK5! +M]9IE8R$N$"]/1Y%S!/KEA1#:R$U&U]>N5S,1FHU!WI ",EBVJS$YZJWK5! M&K0H2EUOE]6.TA7%@_=2P(1=[_0P,:O>"#9QJ:MDS;NT=)Q^R384AA0&" =$ MT%UU'>/\Z^R4L4OP?"F2+DJ.\2RX0X:&J6N7#/C8@&D\ :R46D9%+=,!!$6M M#/"1R630%I&&[%NX)!OMXKK.SHA<8ZA02^FFRUP%&$\2% G]GCCF9AB4=N)B @@ -1508,7 +1508,7 @@ M+R_7&2C7^>Y_GWS@SK;KP$UA)6\)T$>MBJPTR@".F,FF!:D)R?!\EK;$ 18U M52NH &L#E)+K;,65C"1Q>=;D&/6+"#U""] N-"2D1BVJPTTVA:A$*A5$YZ8K M-0E%,LI2'I>8%"7*4H/7&6\S6.2A:N(A0O^@L[6_W!1&X88U[:M1A3F"S R5 MR)B2J8WFEXHD!JV"*E6E\>4"471XE"Q"%Y98GI = G824%4 \V4OIHJ<<29U -MB"@\2:'4E%IMI06F7"H[1RI71(B*$LJB2F6Y3EM*"#C>8G@F 2 '* #9@!Q +MB"@\2:'4E%IMI06F7"H[1RI71(B*$LJB2F6Y3EM*"#C>8G@F 2 '* #9@!Q M+LR.+264[Q0EA^D3);;$E'*;"31/CB$K02'*3"S,B0KGQZF-<9&N%RW)="?; M @@ -1587,13 +1587,13 @@ M.ZA;8%XRTF/D>I)(%*BQ"?@%?A7+N- +MBAT8").&)8/\'4$L+N7I5BO!C^)\F4]$9R :/_Y&: 6Y/)">Q"?@%?A7+N- M7P8R0UI&K@,Y*!V ;;ATP1=UP98$_AD(!8A,J8>-0H%[T/^JAL"!H&6J?$&& MT$/MG"!(,V(N,*(G!T43BZQ(4&I(5="LE7N!'7'6]J)T MAAC,0B W$"88'HL%F8PSCJN,L(8 &($P-(OC35MW4*HGG;>0W*'**"%*.?S).0.59#HE"Z&)0(!<(@:H#E($;:A(28[C@!NKE MRE-8&,PN5!X0\ZN\35,IMRX%5@V7 VT&,A)&#A]7%W$Q@4F"7G!DO7G!DOY @@ -1858,7 +1858,7 @@ MP-3J)4VXR)!R7$SM[UG;!^CVXH"9?1$0MV=+$7&,3Z;;=HG2VZ6V.!P6$Q9O#EA$Q8)B M8;2)9<9#,R$*@;R E:EB:?)DA]&\QS#1V!O<@"[ MX?&92'G3P,**IEWJK8I/&XO8X0-4>%*Q>#63^OVAS7VE% [\KC%+A/'VQF+> -M@P>8FZ,^I(M.,;O$V(I@]KO?%9V^2*\L@T^>A",UIF!*NR[N08FZ,^I(M.,;O$V(I@]KO?%9V^2*\L@T^>A",UIF!*NR[N05_5"LEB_F._FVIT4N9F)B*&"383UU$_=:]"1&FJ2S(] M;*(D(#D;J6%^@-NYG[C2X\K1@B@AHCL1O:T'-H3@!].$AH#\G( @@ -1948,7 +1948,7 @@ MO#5\H3H2%IMAUNU)TN0@\+4'Y'1FZM^YTY]JTP VM"1@>[9GLM?.OUZ[N'TL@5*+]K!ND=K*$TV3%+ZJGQM?/4@GO<\ [.'"Y'-, M,3^LL-@5LW,'Q((5EV?\*34J(\1'7CS_K 8GHRG\LKEY7BI'77V]S,++N+/!/WNY,&X)71[6OZ4.JQ2@MI'JF^%.0\R;V%P6" -M_RD?.TF D-!Q.JS9V;O?L;62+>#)/E0'/>A=YPGBJ=ALEA*KW E'I!J )P& +M_RD?.TF D-!Q.JS9V;O?L;62+>#)/E0'/>A=YPGBJ=ALEA*KW E'I!J )P& MD?WCG[]9/+E0D A1POXK(X(%\%8'I),=]MU&@M[J,KCSE2.#"-0?5\Z(=P'4 MF\B3N-;N[+P#+=J4.\6 ;L#MW&>KB#0.-;0@TIW@)36RT[QFWG0JEP=5(RRX M;ODWSAYG*YW;_Z-%/1>(%CM=$]U::X2AZAQ'%-55K_/%H;B%)SQ1?8H$WCN< -MI-+'%?,H0K7Z!N;X1)Y&# YJ:F7EVW'8YM+3TN" I>26WG51W>PQ]Z[,XK= +MI-+'%?,H0K7Z!N;X1)Y&# YJ:F7EVW'8YM+3TN" I>26WG51W>PQ]Z[,XK= MQ!5?I]T;ZTSQ[!U.JMD5%A&3XJZW(E003N(]X$Z9 I,0TBAHILE#PT[UE64" M-#T8'!3LLVC( Y)S1.('V8I@\#8L4/)/[%:K(%DI!_DS7,\%;:2]<:#$'4L1 M_T+LPP;^$-(E JT$_A_ZM6:VM@Z;YE'IWW;=;=.UY$A5Q02]TAPTPX76;]9* @@ -2002,7 +2002,7 @@ MJW0>Y4&W$7N<4I=!?'DWX-["*_+<(TL7O,)A)>_KJ[@'8L MV2Q:D+@*G$)K]GAMA9R +MY'+W'*(:O3.GYG:Q/58@&=1;X,?2"J_C84S7LFUVV+H$/_:'BW7.@>AMA9R M+6F!QME NY@YET+7%*P3Y;6?\(2GF&"\#XPG/M:*VOO4\:]#O1W-'3J4T\V- M!OVX PCR9^?W:K7'%M/?N-764\5V\%9K]5%+40V').%%><:ZOX4.9]R9=_FK M*)9>MT.#8& @@ -2145,7 +2145,7 @@ M,^VO@\=/CM3ICD4>,LPN%UPM;H,! "FRGCJ/R8BMYPE7_$0%QEYL01!."OM8 MDKH@S:OYOI]/&C:EO0X>?3D(1[B^%A65-.7+YF MB,RG4)W+ MA0R(NALJS;"A$(CO#"DY;^L#NTH&4]?Q@[JHA@ZP>3=K)5U'M: MUF[(=/X.+10C2F(HB5AJFN\\ME4UE!/+SMCTP>WSTXM!CE$V6CY>D_,9RNW! -M+23DV=B'GG).(1MO=^5'R"5R'4@=*%(F?5 0#6)2<>%($&:PQD=ZDE,%($&:PQD=ZDE,Z MH]=2V]EM*6WFBRZTMOLS3RTFJ,=WAR4;:EHCE%GIGN+XZ/[S#G'%W\:4Z&1Z M$M*^)S4;.@FH@#1#:P'9WD1W7+:MV<*62K//];GU=>)TAQ_5 OK68&W3F7LZ @@ -2259,7 +2259,7 @@ MK,I.-I@]O_4]$5@\O*'AD3]7[Z/",S*J@)[S"Y>T^#IS3TRS235+ID,-U(GS9;L/.],SQ/=')XTPL,RIJ%M74*V+P_ -M=W_!R..,DUA36(ZII=B@IZ$-8=X+78C *V5^Q\AG%>@''8AC(('HV)4EI1C +M=W_!R..,DUA36(ZII=B@IZ$-8=X+78C *V5^Q\AG%>@''8AC(('HV)4EI1C MJ1Y8CP;Q,]I@1GN77*&._-[EWMG?;P\9'1H\*WZ:!R.06>W]0&4!>GM>QI4S MMI/8^]+$**<6D12\A6)$R>FA4 'M$DAU,<4? MO:.)MYIY*4!ZV''^_#8CH$H"E2X$(K^!8 27R0>NX2\]BG"$\2*07_UU3\YL M%\/!2MK5ZP9"V '@J0(#4B;^H6+0&@P3^&3,9W+A,=N[FLNP>8S5W6."[/ V M#X_AI6Z#]L.\"N"%J].;G !+FJX-$UVD*J+?];$:5?*Y2!088=9 *3HV -M'0PH7?_DT_G)B^M5*NO!K6K;^\.JXOH5KM\ST0V*#!05YAGL1Q9M-U@?],- +M[%UG%VW* @4C(8=S("SS27E!SU:W(A@U?TN.>+?];$:5?*Y2!088=9 *3HV +M'0PH7?_DT_G)B^M5*NO!K6K;^\.JXOH5KM\ST0V*#!05YAGL1Q9M-U@?],- M#K?72R?:7/^+MNMU# 6T%;:?<\XQR@+EF:$_]!#RM.[D:G9ZYNKOU-01/'K< M!1#$;UB/XIPH@"XW%S"P/LF+%$?(IG>(5D017$='$UB;W M EP761)Y4U*,!H\4?UQ H50H=ZNY6YM6M-,;AV)EGSV5OAN*8C_Q$?TM1,?F -M+HZ&_+.'=##DV6WXS,%7;N;OU%2L/POGEG JFI'UWB XLW=/+2=)B?K(5%1 +M+HZ&_+.'=##DV6WXS,%7;N;OU%2L/POGEG JFI'UWB XLW=/+2=)B?K(5%1 M1'%_R@GJ!9RS7&N_(/"!K;9!YZ6\:9Z)HQX.]315QG!2]Y1N_;-GA)3'#/9* MSJWR9>4#9+=*>MN-Z!/T0*#V\T8&FU6+O%W8%A=506J)K^/$*JGT8&P4FUV[ M+B(;38>F>7)AN#HW2+!U+'U^:R\'@/D2H1:1_BUSXS8O1>7:A@3!)&!!&)1+ @@ -2495,7 +2495,7 @@ M:L7HV:-]D*&R^QF4J<\;4%B>ZI6_G70U/UM ;^P7KTL#0U^XXO*2GX^'U.!X MJUJXM C]/)K2C+7>:GLG=>8 ONOEIA$"S+R5D>T^B447T-+:.K.1J[8YA(1W MV2ZC 0^1*_:O9%)BOQ(V[.X%].[\85OA8.%C9?]7NI8]QFVN1C7E!WJ6FU0M M!=9R:@9J&EQ80NFG4%)P -MV9RXA\!_SUUH+/ESFL[ BQT -GX.((FL5H)DUD*"S+D/"04-K.W_U_70I,3 +MV9RXA\!_SUUH+/ESFL[ BQT -GX.((FL5H)DUD*"S+D/"04-K.W_U_70I,3 MTQOI;? 16:?.@VO+0^F>M/"3/L/921C<'H" X^IML (!QY^7**Y\'8R0CC20HNH.@7HB/ MU,)J]C Y:3T"JLIRA#]1%5/-1QR9O,=AE)F09UV&GY[U #:*^; M63H=F[D1-K@PUM.3(-9#K1FD2)W_#[_W>)Y3,EJKTJ!YM=#$IK>.O^-,((V) M39HJN!FEV\=RH+ '6-,+70G6OA\%YEO50'* -.LP3KL9\E:F:*6OR(7DSC(S -M33I)-!8Y+'2:F[9&SY"=C'J.U<5QK:1>)\RZ+ Y@J?R'ZF6QG.[[PYOH?>D +M33I)-!8Y+'2:F[9&SY"=C'J.U<5QK:1>)\RZ+ Y@J?R'ZF6QG.[[PYOH?>D M&GJU?8O<.)'U+,ZU%+-27H30().&_0T&L>"GA0?(\>PB!W+K([1A\OTDI= A M)1+6XZUVP_G=BNPTY"BMH?-S=0 L*;R6=1,S$ MY>#0XMZB>7$ (_1D3(\HLPT-B#02EE\)K,=:37:X8FY/FW13 @@ -2656,7 +2656,7 @@ M,3QP!0IM"MJ!PRL=%6'PV(7KH-*W5RYC(T][X1'OFJ()*[U49L7\:_#!_X(< MAU03;%ZLXU8,T/4$#PO*:1=Q_KY@QZ:MF7&_[*&%BI29[1B9B+J[D>5TM&RP M0]^*^4U+_Z,EUWWZB1,#G* &&MP^FDC;W_(3B)97:'YJ!W4P]FS9D(TT7&UC M\V I*D\>,"^S1Z6:7 /!8HA$0G[A4^<$EP6?\!XY_!BP5.LZG.D?CX_ !36! -M<^Q(7#>C!H,4BW\+9!C ?>I9U&J'G#KA&?#@355I'UH9T(6&6N+O;C-ZEK? +M<^Q(7#>C!H,4BW\+9!C ?>I9U&J'G#KA&?#@355I'UH9T(6&6N+O;C-ZEK? M]="K!5DVSY6R MP?.+N%)_=8J'RXHP$_L0SB![<;J+2N\KGCO4.J4%[(>3YCVBL7QZ*M-(O/&@ MW-B*PC_=U1N-3&V(4CUI\.CYE<:A%I]:N5D47*NV/LK^L+ VSUZW@H6F =[# @@ -2718,7 +2718,7 @@ M6D7;)CC(P!ISOC1L&+M0:.^3<4_!=:Y%)MS>PW)?'_F9[(5B>+ :292AJ2ZD M6M)_ MVW4,!T,Z#E5I#"'TZ4*V1GW"9("]4)IT2UE! + .IZ#H./<0A/)^6$ MO&@-JT;[(?_L,^,T#6#G*/<.:L6=!Q@8'ENKMYA$J5V5;BI//JZ_-',GG>Z3 M+"06QO5"KUPG9$<"D<1U.FH[7#FAM$4(;+*RZE?<+\6K)"L.2'!RXQFS6CRY -ML;:PA:M@%ULC=AB LU>3 ,XCI/)!-Z<<:T[:_4""-N?ZJ:\VNH+8 K/6(8)JE_@DK?&*B4Z(*S#'%LY[/.I$YD=>(2 M= ZA_J/G3XX\N(O"Q)?N2I%N27H^X Z-ZB?U[2-]C.Z6A.#!*8S#5P\VIT/A2R-^"G+1NBW"SJ2, M3)FIR2Q[3WZ1=VAN>Y@7TC'!\U06B(>8/2R-Z7@/L9,A#,8S2@DK; MX'CD^+K$^AY;NWNH[FVK*(O5WO>-#48VG4POG466;C1V3"UN -M3ZES^Q!&^7:]];XBGX'2L D>U.<,*+&YX1V9'Y(N995=A=+]A,P*9 +M3ZES^Q!&^7:]];XBGX'2L D>U.<,*+&YX1V9'Y(N995=A=+]A,P*9 M:%FQM>>.14T&JRCD739 MJ8<&#$;>>[_I4T9CN; 8D,':IHHI:"I)V2JSK*5TX4F]6 M"3WU5W"IA '5@6W.RZ8JZ8 MB6#.+G<0IXA ZH*8;^^B2C_0^AZ[+8HYX%KHGB)*WN6P8>M$122Z,0[%3YZD MZP[8D7#,U0P#DL"$J6<$9[UG;]5U&DC]%'QP&+!N&%<#!Q>2KBKE3;QY?@O) M +8=H0E=%?9 YB9'XEL3Z^;07/Y?[L1>4B?Z:N(_@%+Q7T=_VFKR7!=C2S)$ -M%9 48OF="*7)N$3L&!$1 7GA/)0))82+O.5"T +M%9 48OF="*7)N$3L&!$1 7GA/)0))82+O.5"T M@'D*W?[W/5XX4FO ]:=75U8)M*6KD4]P40*..JMA]G-6?X\E@7SNXSLSAXK8 M*08$E\/Q6A&.XJ-9Z? ?8"_Y# [!2''1-?J% V>3_%TM":!'])D4D89QPZMF M$)0@RWLO.'0&8&%AF'.\MT)$DK*B0W4!9!R%?/P;Z%JJ]5KQD+$64Y4)AK^" @@ -3158,7 +3158,7 @@ M_(3N'6Y)K YHW'=GRPP6BN1J]4R"D+M@%-.ZIBW8D"1+V,/W6*@_UX>_6\3 WOQ2R -M9S]R@65+LK^&M=&K9A(8IX]DT9"MLH+XD)F758' P.AT(3!HXJ34R('';=A2CNZ9I'MPM>]KP67^7 @@ -3171,14 +3171,14 @@ MMR/PO'U'>;NG06S.I?^1*!O)ZBGIN)8=I_O;-/8A?Q2 P2II2OB(&SS?$0@= MX(FN(1[RVZ!F"=PV#5?3...:G;88_5IK9]HR'#&"L__X4LYV:.187:A)=II> MPN+7Y,OE7@*,]3LR)L*CZD_T!+RHB'ZWM^6/HE6#8W$U66*E(+9L+).%5HTZ MXBV3XDC'IX!_\6[OM^ /"1JS)U-FCF'137#.BT]MBF.&M6(0[O=B)OX,6)3E'$+H 9T06XYX[E[@M?=7_W0&DVV!ASI.BFAV.0CZT4A@]QP1%/I_W?E?'%&B ? )KMZ\'N7G&6:D6I].N MGJ+2RLPKAV#H\YX*?# !]*+SP")7)P07O,HZR&+=&7HC<__&&3 I2OK(>I588]FH=% M.G<1?= \22B[A[R=V[O<&\40V-UQ^ D];_ 06@P07&/SHMR0 -MI/#(*U32Q!=KL[+796,.!N-PF\/M)(QS;OO[E,EWL1<"BY.11D<[BKS'0#H +MI/#(*U32Q!=KL[+796,.!N-PF\/M)(QS;OO[E,EWL1<"BY.11D<[BKS'0#H M-J ##J5)5%H7U0L<:*GMWMX8F)2I6UB=%8=K\D ?N*GXRX2=/]E-:2/CHM5G7MB&_G @@ -3240,7 +3240,7 @@ MK4LKCN8JE^H"^+39"/KX0.#EWBS?X3_OAQ72/#09&"ZV0^=,JOTX0WU-,:$6 M0Q0?CAZ-\2Q8EB;P_VM6%<:R55Z)5U"-ZI0D*2%/UT6SN%>THAI3,U%AU&XR M\#4S*8W'R>ZR+33;YVJ*4/4WL,_)<;T.6N3G4,"9Q%^0=275B70*FB'/]C%@ MB58).P&(.'CASLP#U;Z+ L4!]TLAJH??S?FW(W:\I88 Z!TELI&HY](K:P*6 -MU;>%'@<$8JNX9@AL*HH36@&W.(\V^TIO?Y88IUM?A$3#CAS!X6JKE_XKHX? +MU;>%'@<$8JNX9@AL*HH36@&W.(\V^TIO?Y88IUM?A$3#CAS!X6JKE_XKHX? MR+OJ'@?\#IX<(UGO:+NJ3ZJ7+7<]=WT7GO? CZ=.%X"+NCL6AZCSAI*[G+1, M1,>-;3+F)2%ZJ1\AM-9,&V^K)(99Z';\=_LS1LDN''O.CXLSX7C1:%Z OLT6 M8=HRT/ZV?8!=GY\["=3P@U,:A:=\U5AX4A10-TPM((ADSN%T1&@L6JL"":OZ @@ -3284,7 +3284,7 @@ M<[:0JS5)C?J]_K) ?@2NT[Z(=>9H@T*C@$Z+2?XX5 M7=FNO*H!T9755Y!Z:I,HD0.RN/PZ5!WOUB6$,&W]9_'!B '/1LDMK9O-ME9# MUG>OD@53V<9NM8YA.,!ZEE&=5)(W.-_O?T5'5PC)*(..)4[*VC^^[3C2K?Y0 MA(DD\/H%P?7R#.!K3 CR6,8_PP? (ON2]A4KV+C9X@9W<8%%XD]2C\@4^^L# -MZ]'B)V<%%5"O))KI0E%'=&:Z>D5;^2\1_OTN(2DI"GH'#HW$!8#$.CH +MZ]'B)V<%%5"O))KI0E%'=&:Z>D5;^2\1_OTN(2DI"GH'#HW$!8#$.CH MF[W9B8CLH*02O-&$(GKVY;^ZZV@3GET"UQ/EGK3^:^"Q=OS:7*.M3DH[TEP/ MN6:1_6+ ZDP)"77?=)R6V4>]H=FC*>I'VCX2O>V)..H5IJP5%[@&](\: ^A[]W:P!/L-V3J@=W_+G*S_ @@ -3338,7 +3338,7 @@ M+I"OAWT0V7RYU]JB:ZG2&TSO$H0^R]4V,AIQ.& MH[ V6LAN5 ZCKY0KT-HRP7OX9;P9O':V;DN429BBFTAIL%$&$UM@F2]'MS<) M)J*3 Y(G?>.QFB'/6FHRO:SAV:DR&1B4)"M9OADFXV1!< W/&C@R&'I\5UO* M5K(@BQK0IO_)I4XU#=H"'"C+>28PF/.PJFTIR3',=S;KXAV&)M.E9RV0P:(\ -M$H"B).@J@XGU;AKJ_ /^$\L]&!-,M>RT'7R\@,G;X@NXZVCW8MX(]>=.VV+ +M$H"B).@J@XGU;AKJ_ /^$\L]&!-,M>RT'7R\@,G;X@NXZVCW8MX(]>=.VV+ M\$S3N/_/';ANW-AL#M0$6-P>/#)R"N40,56BJSS)*8""_AO:PPUNF F&3OR\08WTR$>7T;2M M[R]#RB3!F4'VT&]H=% _1BR+YYE_.QZP7._1?C!ZVIX'KX M#T*_"&;6O0K9>TB0'"%\82:?CT5M6?IZ0L4E51\5D,--=9"?1?QT=5]NN M1GL,((/=JT[CI65OOZ,@DDB?=H.L;\U_K<^T$(PLZ]F2BIC%[DJX)+!.PL7$ M[144L":S\SW5&YB[5,>K@Y0"K!#@,V489DCDFY-;$K3: P2\3&OR(ZS0>0G2 -M,&BSU==S*F#]%<@]7K AY87/@B2Y/IJ0?H@Q7JD33^MYETV! M4W,P)8,55%*52;+?_*%M2HANGE?AA6K^Y\/A7V@ [L>@Q1\RGB5H@]NSJ-#5 MHB^X\F8519LUR9;)%3&:25;NO$>K_4'OL2S&K=+PW@ C!,QI$IF0_YWL47S) @@ -3480,7 +3480,7 @@ M2^5[X7^Y)4(SY6( %!UZ7.WOVG\)I>LH!E#"(D'STU-F0LG,"#H4$K8I8@H5 MJ ;?%O88DA\%^LO5F:?M^S\]GB.$9)N9]S@#X0H5@8$.7"I_NBVU'[=A%Q S MQ9W5<.(!M$YZ'<(RK=,H_,"K/.UH*.TFWQ4XSH3&PK(VZ9#R5VFVO. \M\FH M5&O\F^DM6IB\+:@%$EY5]HI/]X'0=9,_RP]T#_W/SEL;W9Q=+;\I9;NOXEEI -MC;A6XT//'4_^)H)6,RF)H]8[%7-?Z UH@4=#]M[6PH.*EB1?G<,#D%])S:G<6*1X\I^\:!N")E HCI:DAG0@-13">?9BT+J';FD%83C;J1#R M^G$6*4W>:DSZ5&9]>$665=-#Y=7A&N]!UW$@2\ -D8$5:*L%3#R<^A'^FR-9F2D M/9<5>6HGWG$1$ [46-ARC8-&02&;(P2]PN$#MGN#8JG-0S:NN0:09[$;PP&9 M@&HC%0Y'&POXM P?1Z.^4&"+R$?E MA]AC6O!+\53"]X'(=TI%^*@T;,T<8A -M-Q6%(&\U GA*)7H)W3K@K[B0NKM7T'-V_:B'QA;*6JSC"'XM9@6^Q+D/^B:KF0)V@#1*8G"4C#FU @@ -3522,7 +3522,7 @@ M;C83*8&S/(YUHUYZ\79DFK#.)YHO5;0:@,CH% 4-L#'B%2[!C1%P,OP \?N* M)XM@I0@^%V9$1R=K?A()O:&#G.23OY?;'V34KCG_OQEK$I@?@S%6A(@8M"[-B)%+&8RZCH -MO'.5+FQK&)0D6ZH(&$%'44AX:B%-^>5VKSZ 8Z#XIZ9>96Z@?R[ +MO'.5+FQK&)0D6ZH(&$%'44AX:B%-^>5VKSZ 8Z#XIZ9>96Z@?R[ M4X8]=W8UL/Z4(%:H<8KX1[^$])XYA"E% ,RH%BA!9K:\^&?60TGD^3<8Z2-9 M37B*OKNQ>^S=- -E?;B?,K:G$M)>1TA&P$[F]=BO=,C5JUI+IH9.SB:A3^V9 MC/N&%I4U;N5@"9P\R:"C526$JT <;]=$)1WCC22#(*&$KANVLE3 P=S6^=J_0CZJOY%?@8I$Y6O^YJWH+WVD M7V&!RQB^<@;>,1><%1[ 5O@(,$%B-_Z7 Q$]G<-OJ(?,D]0D R//@QX2[C/3 M4=Q# JHS0P.H^\_8YR+\?@Y??^Y#%(B*18+W M[R,A8+16VV0AC:^FXF$E45YV?O^JJQ:ZH"U\([63*1G5$71VPI5W]#_90JOB -M>TX/X4MO^U.T"WS%V;S"1_Y1O-&MEC*+@^#W[&+$H03=$%H5OPGW);5RW^F +M>TX/X4MO^U.T"WS%V;S"1_Y1O-&MEC*+@^#W[&+$H03=$%H5OPGW);5RW^F MK%MK>VW-=TYRRZ85*Z W=XVL=9EJ1>7^*!)B*,(TSDDY:(V(9R^&F >EZZ0CQ2#B$]>A# B\2_BX%HI+*1&=!!PLAEU +M:45#0QL2&>A# B\2_BX%HI+*1&=!!PLAEU M RQVO;>C*/TCI6SQ#P0U,@]A>\?FE2MH,4_8TY.,1"?QMJZ28@NA<)G-YXX= M/,5]YC_*JLTZEU0 P^$C\2,@0?C?][L05?#K&[WZ5ONI71_CI3*HRG^P==]( MOH.\Y\<0/,Q/%%G#K=SE?B"CJ%DG-Y 3),LTK9^YV7G<(^"W/8BT^)G0-A$N @@ -3686,7 +3686,7 @@ MR/?T]L#O/G/JCST)T7F:APR-:GC$>FQ2S'N3*8Q*8F,/:[38F[ 9;]3J>5B. M@OEBF5:26F@^9T^H;H;/C([+;D5XKCWQR\K@G,G_S<$_U9V!4WXW2'4J>,3. M% 0BF<0W@/P5P$TRR]/M4_[^E4HI>[+E79]DPR:U4H"BI- ^@;53\!UX) Q) M+4HX.%,4%+MVG&$]^7P*6@2G+3FE9TD'OY^<3%'$Z3G"_$K2S44DYC;[]K,0 -MBZN"SZGNN@:OUB$$6GOCOZ^HX+MY.-4SM2!#LZ[JEU,>)'(/44VC7^IWW @ +MBZN"SZGNN@:OUB$$6GOCOZ^HX+MY.-4SM2!#LZ[JEU,>)'(/44VC7^IWW @ M"$-ZB,_R N.YRJX4W)X4D^O2?/S3.8D\W:IA[]R.W<#-,% M@$630:LX/=^N6]@@K/&'.A3SP^M%FBO9G]1IV!V6Q;XV-HGQ0Y!%P>KJ+_5# M-R2N04K8'5^Y\E$EY-E!))"#>%+!09E@6(/95UQ->'8NUM)>.%73)?+/C?;> MDN_V/&W>]WJD?=)L$=*?XUU4?N]!F@!>XJZ+BS!;]Y-3\>=9U4#$T6S2NF%= -M"/!A.;A)I$A3&D2!CZQY^$-C2J,QWJ7GJZF.[,MXZTL<*> [9TYAH//QHX0 +M"/!A.;A)I$A3&D2!CZQY^$-C2J,QWJ7GJZF.[,MXZTL<*> [9TYAH//QHX0 MV"?-+,T)9AD<9)CW2GW$0$&1R. MR.I@9@X\<^*34#8F*=]K7!"78V\1%0QY.(*:NFN-#5?>T-+6?2)5&3@& M*Z!WMR'1\9 I)+0=#RCV<9]E!&PKVX*@"3W7*+[($RT6AWU5E6O8L(][V:XA @@ -3754,14 +3754,14 @@ M#5X%#U]%:1?61TM3\3-%K_M:,BI,D4M;,:3KL!\+%$-RF?F"BFNLZ)_'P_@* M(/P[2V,DAWA^\_K1MAO5X;%QC0XNXY9Z,G%'.=PW/(_YX#]4!>5QZUYM..7MNT)(@#\;UX"V^ M0D;-#\W;F,"J2)T>U*//$9-/W#/.+C3E33^E3#X["]SLNPC]@[7]?@RH?+OD -MY*8IX1*9WD"^ SAH9_VPSAOJ48(MD<^1L'N])6-J JR1-,CJ%Z+XFM!: '? +MY*8IX1*9WD"^ SAH9_VPSAOJ48(MD<^1L'N])6-J JR1-,CJ%Z+XFM!: '? M2<;LJW$,=!Y_N.E03WDAMCP-U(9/E5\. ;=SV@,%BB[%#S*F"78#Q744&GZ_OKX^ MU"Y?SQ6RLI3P^^PUW'M\0XP*==[QEW0>CI>=]D$@_QM H.B\+>=BU^9)/IE# MZR?H\-9&5Q1*!ATENU5+RR*/!#3O\D3P$8K+K1;7"7B 818USJ$7V2'^6X&, M6'(7-H9GR:=:=V)89]CO4VN&VB4,:4PR9#R)OS^V6ID4(GSG"Q10 -MDP]X@I%GTEW7C+1S3@W0,&:2^YA@1;_[^U/XNIWW,2&NI ?MB @MP>B:;L3 +MDP]X@I%GTEW7C+1S3@W0,&:2^YA@1;_[^U/XNIWW,2&NI ?MB @MP>B:;L3 M$,G_QN('TQ"UNLQ,'YM$AS>W/,XJA<0,3*XQD7YN5\I247L7E'\YKOX!MD$A M:>/N2SS__LS-Q+&_1QNJ/M$6&M"<)]T9Z1N4+@C]\.OY*VBN0[!MO5'-VA^R ME#WQ(=4&31%O81FHSOLW4!F+)O#YS55U.]5<10&L\9)=OK&8>T\;P5K.4#C"%#FR%6PX&0^].#+$*&4,6?ZC*H)35PF4P);71 M5&<_HB3+9WZI4I@']4<+3MLEZ4E'F@T"5W<9:\ \\)O*1V6O1>XNNJ^(4^^N M1N/AZOACB^ 47+/:H@:W?^#^1QT/<9B^Y1 '7F& )\CXRI,0^8B&M]D%QT3+ -M8V05/S./3<,'(U/;>R-R%^1W)?69]0E#3AM+,@NV=[Y%TZ]V6BZ[!'JQ>^L +M8V05/S./3<,'(U/;>R-R%^1W)?69]0E#3AM+,@NV=[Y%TZ]V6BZ[!'JQ>^L MNBE-6[(W+QR3K4\("\'U\+@AU,PCAVW[T!>U^&3B;!;8*78(MYA\*5K3Z93: MT,26[U#?NY9"HRGR2$>B"A6RU;;>TT6F\9RT1L6SM/\>> 01NR[U8)1P;.@= M>)V4=A+]O,0B #G1!(.6E1AEPL_!RN8RP>_(*;1GA([X'*D$\=:!E(6?$] 2?= M:?W09B-=F2//8?6K218X0$]3%_)$3? MG2/HXY3YHO3TX#3UMW8FO,L\GF/I0"I_ FC-36;^VH6 -M7F.$LC4%F"\*IU(O$'6ZK;:K:K^=KCW9!*0W0C* I\3Y0@'?VGI]?6\3&69 +M7F.$LC4%F"\*IU(O$'6ZK;:K:K^=KCW9!*0W0C* I\3Y0@'?VGI]?6\3&69 ML][F852[K^$Y73U2^W'V3# XLG# MV:A'DFQ-H(0Z':97R#]F ,T!8U:)0VVE)%($ZFF%E,)C%;N++V;(_0/@^^ R @@ -3874,7 +3874,7 @@ MLYHM,]KQR/*,H&;J"GRV2:/Q(>6#IJX4N^ T>"JGV^\.9\<;SY5O?XU6? A# M+X/!T\+5@X5MK=.-'PD"T@1G!>5FH0@9#+PUOU];; STC!,!0U] ^P2G. M<'D.=!SKTN$2DY&O2L1#1/=^&#@-4881IEE1N9LZ->\CB.F'\I"3DI.' W1(>[4Y5A=BN_' -ME+$'IEW&[3ZK;-KG]1!#82D42([,DX'%M.%>"O"4O9#W*$C2GC*8WY&K]!A +ME+$'IEW&[3ZK;-KG]1!#82D42([,DX'%M.%>"O"4O9#W*$C2GC*8WY&K]!A MJP,]G,6]QJV^1N*TP/^)7H6K=B.C!,!1D;JH)O.Q[ \T>H/R=4<[%3Y:8U9K MLR @@ -3893,7 +3893,7 @@ M9D[VWMDS*_K-9P:#^=E%L4D6,V6QT6DY$"&#MXJ!NYQM.^\F-A>*M.2;W4F# M2$S#EJ:0]I+EG;F=_%5%Z^NX>":H35KID#ZGU[=#5H5U,"N14]:Q!N7(MV\[ M<)M;'&9\*OX\\6T\4_4J-"J;VE1U760XJ;E:M:XLXPM(+/73*[V$:*2;0*F/3DA -MT=/:&C!I,[Q9^0UT\%\@CJ\:GJD/QOL8!#*@,$U;]P>W$OE-XXH +MT=/:&C!I,[Q9^0UT\%\@CJ\:GJD/QOL8!#*@,$U;]P>W$OE-XXH MZ')VXO"?@@.Q4D0WRBFBA69;3G#.)FO& X:34CI1#07F0J1C+.]EV#01-?L MR+Z=A:F59)H#("0N^DJ* H@G9WX%)D-S4+_H$^Q3U-\I8^RU6&\RN+QAGM> M11YA6*A4T93';QADO*'9)..9>\PI7._,K4MQ,2+:)T_8;93X.V*JZJZ+0&A9+3=^>3XDI)Q^7NB0FBJ#<1E\13O +MJ5Q?4L!LFK(;SDX2E:&W(>*JZJZ+0&A9+3=^>3XDI)Q^7NB0FBJ#<1E\13O M'PIIO-^DD)0 KMO;TQ\2E3.^&UL+S;,8^^_E$=AH&(YS5GT[5B.$V))INIS? MZ$AAF,HW)EZS=#JV.E_=J5%>1U:9'&0JSCUVM<[<#P*[$H/_KBM9RB+_3H0E M0267J3U<9P73I =9H/;V>KWJWMYFZGTJG0^E5,U/;LB@'*"C;A]JVFB*S=*@ -M6@28Z;9Q*MM[+1\,3@>R^]<"55R>J@UF0C-19Q94<'D*0\^H;&9],D_A643 +M6@28Z;9Q*MM[+1\,3@>R^]<"55R>J@UF0C-19Q94<'D*0\^H;&9],D_A643 M0]3-@3H^3V4HOD.<6W\$$:1C-L!Y)->O\SAA3#C&94[9K#6C>> @+W]E7XOP MV:8O..0KR7_P*D O&(&&-;W&Y__N@P//++;4-.29@H&3.EU/3H&_.^JB!3-> MP)GX'YG4&5?A_9?;O*B7CZU.O"H%Q-$2@PZG71#&/%O@[N!N91:E7@^ +MUPR(W;;=3<04TU86S\B"Q3=.P9% [\<7R3EG2^N+SB7']_AF'TM!0-'>7@^ M1,?F16DH/PN7M+;9Y41GL?5'7;J /RIB,RPB7%,I-QZV@&"H\/I+%T# M[:&R1)XE@#Z.IPHT6>BI%N;>TV8<%C3OC:?!]$:4?#E6<>QKZ18R&5M$JFG! M)X+3RT'QHFUUW;.@3%^FS]B8?;96J1$'6&CC"B+1@OMD::%?]P]G.6HVSO1M @@ -4029,7 +4029,7 @@ M_J0TGIA-3';S_% )FI@4I&IDDSWM@DO!Z>A;T4V43@^P:8,(2_?7GHI_4R27 MYF\!_XX!JJ"CK+1KDWKLP!\\$LGM 4$:\\O[]JD]=46T7>5NY 4=$>7SMI/Y MM=[J Z->@Q4((?G?Q(9J2,Z1ZV0E48,^4UCLLWN+!?D>*>SF/G1UP*"70M%W9,0;J1U1K^84-49*@54E+Y+"&$I6R<2ZF.%?PBOJ(1^0<8]- -M8ZO6GMI1BNL $4;[C@9\FZSEXD(O_$3?'W6,.2' 3=HJ<#V5"Y<_=AX CFU +M8ZO6GMI1BNL $4;[C@9\FZSEXD(O_$3?'W6,.2' 3=HJ<#V5"Y<_=AX CFU MSP2G8:S9%GV/-MQ?LHZUE6J@TACCT1$,PWP&6PPV%?DZ/KA;3N44#5&>LIU\ M(9DF9VA7ZI'J)KQ-7B[QQ8VQ=9$QFTII)9Q(4Y+AM(3BI1Z'/4(ZJ,/(@7'/ M^\R\;,I2A,"-20F992R.HH* M9;7W^+U)N):>:TB+#Z"XR!FX3Q,FR9#ZR>[ @@ -4055,7 +4055,7 @@ MKZ^$24QGY?M#?7N=7F\&-K'$>NU]K-XU=6[IKCO[@:U[PFG&0&XR.^VC+ >$ M^T!CJ+B9Z?Q23 Q::V^_Y'6SX 6L\+Z) 6DM-8+HN-F-5 MJI0]I[? *GG*8?-):QY3[X/V?T;-!L-O^*WI8$_K6MHY(CJ$?V3<3&/!),U_ MT2+M*PP@B9QXW*^_PKHT]_GZ>,DJ,-C\?NO=Q1O3P-UF!#,?8]=SM)0B[*!J -M?HW>[B.'OJ <^[*+ @_^+_;351G.3MNMH&&"=EN>KK7UC[MOXM5B,).'N?+ +M?HW>[B.'OJ <^[*+ @_^+_;351G.3MNMH&&"=EN>KK7UC[MOXM5B,).'N?+ MW2PZ.RIP8O/ZL?C;H[>G$=M\5@MBX 4;+?S^"+"=-1+\@I^HU*8K?7M@1H7^ MFRI*9UY6I/P+%" 29G0F-]CULAA M)(TX9WR< /*5N&[+AC?)_8<1:754_>C/2@8[NH8OTS#EU5?;FWF$B2;V"@]% @@ -4068,7 +4068,7 @@ MH?.)C?/FL5H+$YXXZ)[B%@-2YA]Q\APX6Q0FJ'#'\6&L-V >[-D919(JYD.Q MV;30N!(E<(6BM!DPU;2E55BH8>@,R<,'ASIYC=(S:$RH]:D]+UDP7^,I!O;X M4+6A^1O/ZB;CYC\L)2B?0R- %2/7F1;>K#B^6UHMWD*V?KND]&;-2' M0QYPTC<7>5#B"_E"FLE4@VX,S@;)6@RL6=N=5V *_J)+;PVF4+6S/#?3MZLDX=%D&9F:D,VL;Z+ L'>X"Y:6QZ0TAT]])^ M2HR9XNCQ]C@ 6?1/"L#1->)6H;ZFU9[!?\5BWDAYR)7,1#?'_-;MEH,O#)E1 MS26BG6_\LR;H)I6]:WH&A,-$HP(!1WDW9BD1>MRN#HNPO#/35Z:O.CL'?Y+; @@ -4338,9 +4338,9 @@ M9WHH^K I"B[NO]_)8I\37_GW)C>,39!&!'0;6U$3#:M9]P/M![2RT:LWDN^: MIJ49UU:,$WB2 I.%9BS4"3J%1AU>^.N&:U! M3H$VS,,#VOHTQ&%;+4A4M"V/P^M_)(+YO)#A)5L[;PY9J;5N8,X7CA0*L0WK -MD.Y1#9S TYHUK/)#XX/-.,+91;!D"%0P8%IGR/A"7"@V])GQJI?_'(B8M72 +MD.Y1#9S TYHUK/)#XX/-.,+91;!D"%0P8%IGR/A"7"@V])GQJI?_'(B8M72 M#BCY'C0#$#!A)(K1\X-_:8ERYJ&(WD5"E3LP"KS&;?&"BRMB?V&X[?3Q'XQP -MFM<9G",/ &%2A@*VV980!O/R&G(1'BT1>L.TTWAW.T#QG967!^],)"N16-) +MFM<9G",/ &%2A@*VV980!O/R&G(1'BT1>L.TTWAW.T#QG967!^],)"N16-) MOMTGQ9\8X4K]9/*\.M[1+&(SOK$J:IT\WJWQQ:O92G0DH'_%H)&6ZH/TL:5F M?&@IX+2A:QL_S7$8AW'5Z'"0,C*2)SQ-K/;\%L"MHM!=3U+1J:N ,LUTA1>S MQ>]-?,!'SQH!(B"@.A[Z@1K1'X@8V.SIU/)XTF!>4A3W]YTF[3U;Y2B78FI+ @@ -4483,7 +4483,7 @@ M2U&?S3!Z8_V-<*- $WDTF5CX[(G;(\=C#6D;PJ3J"H=OWSHTJHIWA2N/;45Q MW!B+M9_H"1W*U/B]:5GJ"B\Q_YG:+O9E,S<6IBV+97=HAF?MD*YG2^Z*>1#_ MLG60@N]=29UVK8(G[="G@"4%<:=!$;UI6;O8W<\K$J^UG)HL(H7-U+,R9?6- M%B3#VD_\/Y)A3U($K58\.=_'6I'X\8Z^.YAHE7UMO+CF=Z*=^S@2=T7 -M!-(3QD.VR:?N%9S%9O+6BX$8/;Y*WE&[Y=X8':MA!-&FXNJ?K@<784F,'7 +M!-(3QD.VR:?N%9S%9O+6BX$8/;Y*WE&[Y=X8':MA!-&FXNJ?K@<784F,'7 MP,W5'?IX9\$Q]*9J8K61P\R0WOOY^A;:+5&EVCLN.,.7K:4GKZ(V'R#5C1C^ M7!KW?)IA/<='CQ7#-S67@?GS9XCB#2)_4 ]7*N>/SJT2 N>J)G\YG?#G=>^[ MIRT95(=D1=-1$03%]_W&OYWQ+#Y8&7HQG/^0*)&D=NA'"RS[ZW=JUJ>+A8GMA*ZV9 -M;@?M-#G=XOP" ;&4U(13FBIA%#Y=R!_D!/T X8."B[V@O@P!"C0?W%,.90: +M;@?M-#G=XOP" ;&4U(13FBIA%#Y=R!_D!/T X8."B[V@O@P!"C0?W%,.90: M)+9Y5#5(HK)29?2 W8D]/B7D*!U;X>0]--O4>NXD@Z/%6<+*0P;!S9Z+;;*B MEBG'M<=-HQJSOB^\>R<=NY#[^299#C_.?N1\MO085U%%0X+:9Q3 66JI%[Z5 MK1SS/$A^G&^D'T91!Y,WAP2SSO:@;P06ZJR%VME@RZT4=[V-\6LJXPG\.0X0 @@ -4622,14 +4622,14 @@ MXG%=&U;*:"O=7CVJ0WT?,1J#UCKQO7YO[MV3Q5F4\=&Q (&5\8/SZGE3*]]O M)%_Q6KTU*3^>+CM56#N)UA.L:R &V9AA@/HYX8VU4*"P0.E&D6_*7I^/@EL[&>*K5;,RZDAAV,;;HR% -MG_I*H[J@U&8R\Y3!U;BZ04+_$_0C4^TX6NMG+UM1:K(B4+L>+<)IA.]!T\. +MG_I*H[J@U&8R\Y3!U;BZ04+_$_0C4^TX6NMG+UM1:K(B4+L>+<)IA.]!T\. M]S_DI(,# QX,!# 0P4 " QD,%##0P, PL,'##PP7@$!@D83\&@!8,9#$XP M!,%0!,, #'LP?,'( :,+C%4PH*& _Z5 _0/0_P#,/P#[#\#] [\Y?X_]W0>R M5L@Z(>N#K(T0C"=@/ >#'PQ],+S!Z(",!Q-X@P$%)H$"DT"!2:# #H,".PL* M%0Q,, C ( 6#"0P^,/2 _TG^N:9_KN'WG.&!/_'X'9/? ^'!!.[7B@W<"R+PQP_POSCA?HV#_<4)\XL3PH,.W,\?X_\P%X ( MBPJ-#(\.A02'!H."@*$/O ;GD"%@!!@#)H I\ 8P \P!"\ 2L *L 1O %K # -MYY<#X B\!9P 9\ %< 7'3 "%H 58 -> J\ =O" 'C@'4 SI:'X%U !! #C\$[P1/P N>*#SC?_8 (! ( M H*!$" 4" /"@0@@$H@"HH$8(!:( ^*!!" 12 *2@13@/9 *I 'I0 ;P?UKN @@ -4639,7 +4639,7 @@ M2TC,Q:#OYZ,'+B%YY PN(;D4!?W/^4'=S:_@;WK@;_KV_](?(M/0?WK^7;_Y M+_KS?]$CPOQW/<&_Z*G_1?_J7_1B_Z)7^Q>]Z;_HG?]%'_0O^J1_T1?\B[[A M7_2]_Z*?_A?]YK_HS_^F!W[ID6 A1P/8UQ]@ 5-P\JW!WN4SX%4$>Q?'WVW3 M4EA(OOW5EBN'A>3O7VWG"EA@B?6^'2PG(ZJV#@L4H/^Q R?@-N*?=L$-+-#[ -MM^?9P,(!IG!_VM-(< U(M2?\=CP0$'@(0)#2GS:! 0) +MM^?9P,(!IG!_VM-(< U(M2?\=CP0$'@(0)#2GS:! 0) M+?"GS9>+ !10_&EO]H/;F'\;/PL>C_2W\=\1 %/8O_EC"^'N;EV#O?>;WP\$ MX/C>N>"2Y:X/$QAZ=]9[@] OPRV" $_8?8_.YX#@)%@G[+.2CH%PUB @@ -4666,7 +4666,7 @@ M_XT\M[FGN/.!\]T!&1D(:8-^"=CN]+ON!*[_RM+[.N2F!R!L =W-!WP+N01#T@% 8!(%/W\@@\&L(I(3XZ:X$1^>N!'G^*G^WP28 +M0?\MGBKX+(5L@;.[\FZQ0> 8!(%/W\@@\&L(I(3XZ:X$1^>N!'G^*G^WP28 MRL7)";+QH)):>JB6< MO.>Y4=MDXDI9S6@L&/!YZ5F'H?Z&#KFW]IH,"Q\3ILE3+/DR)T#^(VF 5XEX#7T+GQIYURB8EH2Z@(7(\ M'!8C,V'.L.TF]-[[ND$Q="2$B$C4148K7K:M+EY5ZVP;JGEP%3XA4 *8W)*7 M9](W3G&OS9 BDQ>)A4 +C[0??5\\266W3UG98A1>H_/"\#SO4LX?29#Z-C E M) #;4P'.*"A)$LQ) H2/:$+",Q:N@1?3PK.BX]?.&^1;6 +M/"[*@W?-J"V8K:6L*U)-"*67>)$LQ) H2/:$+",Q:N@1?3PK.BX]?.&^1;6 MB8M/P* #$'<3/NF6>IY&I3:V0C")ERQ9L<.?$9^+Q94 MUW"B534L$.,\UT7:-G*A@RBS[*2# %0N158&?$!9HACVZ+3Z^:Q&HZ#_5H+E M1XT?-! 2SY,LY:KB;#=82+?R6,,S?]&:+A^Q7]M)H!&.2+.$1\6;T6G.[ F5 @@ -4716,7 +4716,7 @@ M8_J;K&LF:([2S/30TLKMN,]X+PUD(F7HY<+U>@ZX;\79 M5+'BB"41P:FO%5'UJ9W MRT5;-E:BWQ^7I$%!Y+&W'+RTK:,V@.?,O.DI/Z< MG*^;^^IP[32HI8\1 /70#== O\;I%SJEUKCQOHGJ6ABTV>H)4MPEI-ICPR,) M?#HJT9BOI*2^2W?T?NE./=_"ED5,W4(-IW7\":T6.;;G1N)#GSG^;A0J8CO: -M>'K53NIV_24@0>M3<41.$.%?0G@$98BV:2K)-TDP1D-=<5D3''N&:6*F<:E +M>'K53NIV_24@0>M3<41.$.%?0G@$98BV:2K)-TDP1D-=<5D3''N&:6*F<:E M/S6C>RY 7YT-NX1?OZ@SB3SL6%K4IR"#X]SX>57FZX0J--^";2J0^;T9==U3[JM-IQ#[S2 MAWS%"7(IOQ^CT.9J->IWD9)S17QMDV:P+ONP4[_@6?15C@VURV=#K3?D]6-: @@ -4772,7 +4772,7 @@ M;[IE:>Q!M>@HB<$INGU:@^6&:AB)??N=;%_(@*B&MWH;Q6JGE(N<"V+J;+?:N +M^$N+F?J5R10V*0VP.U2F.[P@7>??N=;%_(@*B&MWH;Q6JGE(N<"V+J;+?:N M6K%ME9?CY]7,Q*VU*>W&GC7:4?NK$DR,,^F$_8+JLLVOW@HY'%2X@O: )#_[ MD3F:@I^/+TV/R'[;(#D2\>23]$CJ>BF'%9LS#M\:)L_JH^LUCKFRVR+MC6P9 M_YQAH1%]2H\0S3J^)\)#%(DAFB&N>1S7\T\K%IS/$DS4Y?4RH-P]U8LX;2,MBB]G@-L_!6!7%1;U[7/:[5=RKD MZL-57WCF'W4VDYZ#N.N$CZ26?E;Z"6([OQ7S4MR3 P+E+^ZIWJ%< MP)T7N/3"//VE9.WAX*%+O&QI]IE68AUBK9,4=R^S.PF.#.K6O-!*55B>?\Q3 MVT9-8Z5!W;[K0G=H,CW,;&AOGZ'?D&%LG!*Q@GK!7JK]#S64UFW9'!UL&:IA -MK1/D?I+B]I6',*+/*A/BY""SJG[-\-X[J-6(J"?!DZ8Z;C&0$OU2^^Q,EMQ +MK1/D?I+B]I6',*+/*A/BY""SJG[-\-X[J-6(J"?!DZ8Z;C&0$OU2^^Q,EMQ MA,B;D146K%0#LG=YI$HV#S;W)=S(3=9YI)Q@.K!6:D2;8GE?&T?IDDZF'\_HL&'+='ET;F,,:9'=%.CLR)=EC26K;8LZW @@ -4829,7 +4829,7 @@ M&\@\H_E&>3TC[(&S9>0H-.3F!S+&4.-:$I]#G;WLBNA/CM7M_N1EF>\,: TM-0+"<4(Q6/.MP076%0E/VSHY6%[ M6E<4C>'+K\TU6$T9?^6UX[+,]QE"O8\W>AX/>U8SJ4@!QC%I! +MSQD=\^?-JQ<5%3W;-E0M=-\BIFUG<'&S_WP7PU9-BP-$)XHP<>J4@!QC%I! MYBN[.R46D)"0!$?X-6Z[(=!MG3%P+"LF35'OR*M;>P+-"P< 6^:^P8;L\Q<[ M:(NC'=?+$DF)L AU66[BWG\"6?EP$B]& M7>.GRUU>0MGE,4^TR\EZ\V0:%=>(TTN1"5-8Q6/N @@ -5159,7 +5159,7 @@ MY6=D_#R1Q].SY;OZJ*@^PFO_V]H'"TO^/RKWG^#]NW+1\5,("15\UTY"<7"HG\C/R)@[<=S3LVG*.F_=^N93J*E5 M?%>NJVL86U;6_QUL;3'3MAW+Q'TDM'Z,YH!>W#WCV8/9H?^[$*D36+ @ M_3N(B>5/M._MVTZLO7TM#O[^C3".^?8>\&/'BG#YUZ_/GCCV^''CE'6>/_]M -MKK>VKOFNW(_D1_7-G9N&UX]WJ25C]Y+0?65H!\@/Z.GNFE7HZYT-^G>$2)H +MKK>VKOFNW(_D1_7-G9N&UX]WJ25C]Y+0?65H!\@/Z.GNFE7HZYT-^G>$2)H M,W/Z=SA^_)O^;]^NP4O3T/@V9X>&-N&.\?/G3!P+"FJWO'=L9"0QN_JX^)*Q^EW @@ -5173,7 +5173,7 @@ M_;''RD3XVV\/Y6-SLTL+7U!@0:%XZ1ZUC>>Z\Q*HYCQFP+2\>W.P^+L6<--60#M97B'<#? M (82N X8*9G/_S^CW O&Y-=%8M.FVJAKQXW;&I:.W=N+L:Y*&7=$=L#U !X/,)3 +MG^'7K*G .^_<'_6<>-=%8M.FVJAKQXW;&I:.W=N+L:Y*&7=$=L#U !X/,)3 M8]0&/___1'A7PSWW[#TN,7'B9V'IV/5-H3(&K4S8 =<%K>HX\R$"K@,.?OXO M)6S4\/##WQV7F#KUT[!T[/BF *LK2@GRF&0>=RSF&PPA,@ _YK_(?Y%GX\*/_&?2*C>":7% M?E$7^I^G'?#UYZ*>Q_X5?K^(ZR FX/X#H O8-@SG46 MH/>K\"D,LGU8ML0.__B;&\J/^,% M.,*IUG/ 7,B9X#;.2N?< B[G$[UUWV[O%AUS(_'B*-C97XL*?1CP3X??+/- -M*]H5[*_95SQZOPW_;78O3+O<+.I55(<':O/AG;-[\>%[;?#""B>^^8H+KKS +M*]H5[*_95SQZOPW_;78O3+O<+.I55(<':O/AG;-[\>%[;?#""B>^^8H+KKS MQ'49?&>]RU#W;_," 15H>DFA#RB?D.KNV$/O>9L51#N0[\G;QQX0 M90O)[OEXPO@8U07_;J?& W,BI9=_S?_,FS=7]'^H8/Y3(7R;U<\ZA)_@=D_! @@ -5523,7 +5523,7 @@ M<7QQ3JBMC?01UU'H!*=BX BNX*S8Y7_!*6Y7^"XC5<.8$>/$9Y]PA8V1H!W+ MN@2[DNMCNH$CN(*S8I/_A04%7*]VZ?]G?E?'&EW0%-;M1(ZJVY5*VX>%M#GZ MX*S8Y7]A@?9-#OP7M@#W31C?J.-G(<<%/M9!MOJ?%33M!>:LV.9_(376*K # M,^18'O^(>[I,"ZVRF\%G2^(>[I,"ZVRF\%G255XK-(?_17)?(A1J-GTP:.U!O_&3N;66>2VA\2RRT_^B(FW_ MH6;#\T^ZZ)[_Q#.V>LN\UA#,6;'9_P;M(_OQ\@077PO6?N&US&N%'Z+_T,6# MGC+N>ZWR6J%Y_:]O%JQ8ZJ679!M8M*"6B@KJ+//'1C/[KXV#[$;6;C\->=A! @@ -5564,7 +5564,7 @@ M.AD4J__IP[PB(QUE@P.X*(GQ]W7ITIG?Q^/= >9>8?X1YMAB#A[F6VI%I&RE MY]2YDO/^4LOS7W9]&S#24#8X@(N28/OITJ4+GGMC+0=15R?HF:%.@3GA9PKK MQ<31+O':)+? _-RFUG/C.'YMSPB(8;]WT+$C0>'U"K'H0R]]/+O6R(NRP0%< ME(3KOPMY7 [R:<^MCA\-TNC'G91[3)WOB#F5^$;\]*GDYEY\L=A'(Q]S4G9F -M'2UXKY;W]?D_?!0RS3U!V>"@\4^H_KO*_\!_OS9Q W6".9!SWZY%/8GJJI# +M'2UXKY;W]?D_?!0RS3U!V>"@\4^H_KO*_\!_OS9Q W6".9!SWZY%/8GJJI# M?.F^/6I$UNZZ)M5Y?5!066F]6+1 70OWX;MKQ/@1+OKZ2[_PUD;^#V6#0]?$ MZ]_;M6L77@,$ZQ#I^]5#V6[$][OJ1'%1/9T\'A1])8^,]7ZZ5!&2UXN0."NW MYQP*"K0/U.F=0A=0A=0A=0A=0A=0A=0A=0A -Content-Disposition: inline; +Content-Disposition: inline; filename*0*=utf-8''%6D%61%69%6C%69%6E%67%61%73%73%65%74%73%5F%64%36%38; filename*1*=%37%39%39%63%64%36%33%30%31%63%37%66%35%37%33%31%61; filename*2*=%33%63%34%32%39%34%36%65%35%32%38%62%63%62%37%38%65; @@ -2839,7 +2839,7 @@ Y9Nu45cB/wCX/On6BxzL6f7IvMxPi71x908Pwf8AOl6EvP6D7510FXbsoV91/wDD/nR6En4/QSzO Content-Type: image/png Content-Transfer-Encoding: base64 Content-ID: -Content-Disposition: inline; +Content-Disposition: inline; filename*0*=utf-8''%6D%61%69%6C%69%6E%67%61%73%73%65%74%73%5F%61%39%65; filename*1*=%64%34%35%66%66%32%36%34%35%63%66%66%37%66%63%61%37; filename*2*=%30%32%62%32%38%39%62%63%36%38%34%62%31%62%63%37%32; @@ -2857,7 +2857,7 @@ CCKIIIIIIsj/gPwC07ldsxmsvBUAAAAASUVORK5CYII= Content-Type: image/png Content-Transfer-Encoding: base64 Content-ID: -Content-Disposition: inline; +Content-Disposition: inline; filename*0*=utf-8''%6D%61%69%6C%69%6E%67%61%73%73%65%74%73%5F%30%65%34; filename*1*=%64%65%66%39%37%33%31%30%64%66%37%35%63%32%66%61%36; filename*2*=%65%30%64%62%38%66%65%36%37%62%37%39%64%31%33%30%62; @@ -2878,7 +2878,7 @@ kkASSHfxH+kZj8bhlr7ZAAAAAElFTkSuQmCC Content-Type: image/png Content-Transfer-Encoding: base64 Content-ID: -Content-Disposition: inline; +Content-Disposition: inline; filename*0*=utf-8''%6D%61%69%6C%69%6E%67%61%73%73%65%74%73%5F%32%32%38; filename*1*=%62%31%38%32%64%61%30%35%37%32%35%38%33%33%61%34%30; filename*2*=%30%33%64%63%62%64%38%66%36%66%65%36%37%39%35%36%61; @@ -2898,7 +2898,7 @@ VEEUREEUREEUREEUREFOtT81X4mDKmlVrwAAAABJRU5ErkJggg== Content-Type: image/png Content-Transfer-Encoding: base64 Content-ID: -Content-Disposition: inline; +Content-Disposition: inline; filename*0*=utf-8''%6D%61%69%6C%69%6E%67%61%73%73%65%74%73%5F%34%35%34; filename*1*=%35%61%66%38%64%35%39%36%62%62%35%31%30%61%31%31%38; filename*2*=%31%63%39%61%33%62%65%30%34%36%30%62%63%35%37%31%38; diff --git a/tests/mails/mail_test_6 b/tests/mails/mail_test_6 index db7e65a..4788a10 100644 --- a/tests/mails/mail_test_6 +++ b/tests/mails/mail_test_6 @@ -24,9 +24,9 @@ DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=ggg.com; YkAQVr7zVMpbpZml/aYp38SQcR/X+eYPSMFAe5+E= X-Mailru-Msgtype: 770 capital List-Id: <770 capital.ggg.com> -List-Unsubscribe: +List-Unsubscribe: -Subject: +Subject: =?UTF-8?B?0JHRi9GB0YLRgNC10LUg0LLQutC70LDQtNGL0LLQsNC50YLQtSDQsiDQt9C+?= =?UTF-8?B?0LvQvtGC0L4h?= From: =?UTF-8?B?0JLRgNC10LzRjyDQv9GA0LjRiNC70L4=?= diff --git a/tests/mails/mail_test_7 b/tests/mails/mail_test_7 index 3ddd338..4eb12ed 100644 --- a/tests/mails/mail_test_7 +++ b/tests/mails/mail_test_7 @@ -7,7 +7,7 @@ Received: from voidstudicom.it (host161-200-static.0-79-b.business.telecomitalia (Authenticated sender: ctrstdicomsmtp) by smtp.s2smtp.com (Postfix) with ESMTPA id 67A957337 for ; Fri, 9 Jun 2017 18:07:16 +0200 (CEST) -Received: from COMP02.CSC.local by voidstudicom.it with ESMTPA id md50000234499.msg; +Received: from COMP02.CSC.local by voidstudicom.it with ESMTPA id md50000234499.msg; Fri, 09 Jun 2017 17:56:17 +0200 X-Spam-Processed: voidstudicom.it, Fri, 09 Jun 2017 17:56:17 +0200 (not processed: spam filter heuristic analysis disabled) @@ -106,7 +106,7 @@ Ti auguro una buona giornata =20 =20 Anna Milone -Export Sales Manager +Export Sales Manager --400816-6334-1497023776-0 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable @@ -1432,4 +1432,3 @@ ace; -webkit-line-break: after-white-space;" class=3D"">

<= /div>
--400816-18467-1497023776-0-- - diff --git a/tests/mails/mail_test_8 b/tests/mails/mail_test_8 index d9263a9..5ae9a22 100644 --- a/tests/mails/mail_test_8 +++ b/tests/mails/mail_test_8 @@ -23,7 +23,7 @@ Content-Type: text/plain; charset="UTF-8" "The Perfect Filler Between Real World Flying" Imagine "Real Life" Flying At The Comfort Of Your Home... -Click here +Click here http://www.moneytrack.top/l/lt10VX3615QP370UC/1222A1383JJ1979TG249B81339090GF3323432606 @@ -50,7 +50,7 @@ http://www.moneytrack.top/l/lt10SG3615LD370EN/1222O1383GD1979LL249Y81339090AK332 If you do not want to receive any further mail click here. -82023 Peters Road, Suite 1000 Plantation, FL 33324 +82023 Peters Road, Suite 1000 Plantation, FL 33324 http://www.moneytrack.top/l/lc13NE3615MS370NH/1222I1383VY1979GM249T81339090TL3323432606 @@ -72,44 +72,44 @@ Content-Type: text/html; charset="UTF-8" -
+ Imagine "Real Life" Flying At The Comfort Of Your Home...
- - + + - - +
Click here + - + - + - + - +
 With 120+ Aircraft to Master, From the 1903 Wright Flyer to the Latest Military Fighter Jets.
 20,000+ Real Airports With changeable Weather and NASA Flight Models.
 Realistic Worldwide Terrain Based On US Defense Mapping Agency + Lifetime FREE updates/upgrades.
 Used On Television Episodes & Professional Flight Schools - The Most Realistic Flight Sim To Date...
- + @@ -120,9 +120,9 @@ Content-Type: text/html; charset="UTF-8" - +
Enjoy Real-Life Flying Today - +
@@ -160,4 +160,3 @@ If you do not want to receive any further mail
Date: Thu, 24 Oct 2024 08:56:42 +0200 Subject: [PATCH 6/8] Added pre commit in pipeline --- .github/workflows/main.yml | 5 +++++ .pre-commit-config.yaml | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c661ea8..a2c285b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -41,6 +41,11 @@ jobs: mail-parser -f tests/mails/mail_malformed_3 -j cat tests/mails/mail_malformed_3 | mail-parser -k -j + - name: Run pre-commit + if: matrix.python-version == '3.10' + run: | + make pre-commit + - name: Report to Coveralls if: matrix.python-version == '3.10' uses: coverallsapp/github-action@v2.2.3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 27a9b84..3634a7f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -26,4 +26,4 @@ repos: # - id: ruff # args: [ --fix ] # # Run the formatter. -# - id: ruff-format \ No newline at end of file +# - id: ruff-format From 8d9be71c2ad913651fd9e6a0cb85fe656a85fe92 Mon Sep 17 00:00:00 2001 From: Fedele Mantuano Date: Thu, 24 Oct 2024 09:00:59 +0200 Subject: [PATCH 7/8] Added ruff --- .pre-commit-config.yaml | 18 ++-- src/mailparser/__init__.py | 6 +- src/mailparser/__main__.py | 95 ++++++++++---------- src/mailparser/const.py | 92 +++++++++----------- src/mailparser/exceptions.py | 8 +- src/mailparser/mailparser.py | 164 +++++++++++++++++++---------------- src/mailparser/utils.py | 68 ++++++++------- tests/test_mail_parser.py | 139 +++++++++++++++-------------- tests/test_main.py | 8 +- 9 files changed, 300 insertions(+), 298 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3634a7f..c976c6b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,12 +18,12 @@ repos: - id: mixed-line-ending - id: check-ast -#- repo: https://github.com/astral-sh/ruff-pre-commit -# # Ruff version. -# rev: v0.3.2 -# hooks: -# # Run the linter. -# - id: ruff -# args: [ --fix ] -# # Run the formatter. -# - id: ruff-format +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.7.0 + hooks: + # Run the linter. + - id: ruff + args: [ --fix ] + # Run the formatter. + - id: ruff-format diff --git a/src/mailparser/__init__.py b/src/mailparser/__init__.py index 3be35ec..a80e9bc 100644 --- a/src/mailparser/__init__.py +++ b/src/mailparser/__init__.py @@ -17,14 +17,14 @@ limitations under the License. """ - from mailparser.mailparser import ( MailParser, parse_from_bytes, parse_from_file, parse_from_file_msg, parse_from_file_obj, - parse_from_string) + parse_from_string, +) from mailparser.utils import get_header @@ -35,5 +35,5 @@ "parse_from_file_msg", "parse_from_file_obj", "parse_from_string", - "get_header" + "get_header", ] diff --git a/src/mailparser/__main__.py b/src/mailparser/__main__.py index 657be3d..db9d849 100644 --- a/src/mailparser/__main__.py +++ b/src/mailparser/__main__.py @@ -35,33 +35,26 @@ current = os.path.realpath(os.path.dirname(__file__)) -__version__ = runpy.run_path( - os.path.join(current, "version.py"))["__version__"] +__version__ = runpy.run_path(os.path.join(current, "version.py"))["__version__"] def get_args(): parser = argparse.ArgumentParser( description="Wrapper for email Python Standard Library", epilog="It takes as input a raw mail and generates a parsed object.", - formatter_class=argparse.ArgumentDefaultsHelpFormatter) + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) parsing_group = parser.add_mutually_exclusive_group(required=True) - parsing_group.add_argument( - "-f", - "--file", - dest="file", - help="Raw email file") - parsing_group.add_argument( - "-s", - "--string", - dest="string", - help="Raw email string") + parsing_group.add_argument("-f", "--file", dest="file", help="Raw email file") + parsing_group.add_argument("-s", "--string", dest="string", help="Raw email string") parsing_group.add_argument( "-k", "--stdin", dest="stdin", action="store_true", - help="Enable parsing from stdin") + help="Enable parsing from stdin", + ) parser.add_argument( "-l", @@ -69,125 +62,128 @@ def get_args(): dest="log_level", default="WARNING", choices=["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "NOTSET"], - help="Set log level") + help="Set log level", + ) parser.add_argument( "-j", "--json", dest="json", action="store_true", - help="Show the JSON of parsed mail") + help="Show the JSON of parsed mail", + ) parser.add_argument( - "-b", - "--body", - dest="body", - action="store_true", - help="Print the body of mail") + "-b", "--body", dest="body", action="store_true", help="Print the body of mail" + ) parser.add_argument( "-a", "--attachments", dest="attachments", action="store_true", - help="Print the attachments of mail") + help="Print the attachments of mail", + ) parser.add_argument( "-r", "--headers", dest="headers", action="store_true", - help="Print the headers of mail") + help="Print the headers of mail", + ) parser.add_argument( - "-t", - "--to", - dest="to", - action="store_true", - help="Print the to of mail") + "-t", "--to", dest="to", action="store_true", help="Print the to of mail" + ) parser.add_argument( "-dt", "--delivered-to", dest="delivered_to", action="store_true", - help="Print the delivered-to of mail") + help="Print the delivered-to of mail", + ) parser.add_argument( - "-m", - "--from", - dest="from_", - action="store_true", - help="Print the from of mail") + "-m", "--from", dest="from_", action="store_true", help="Print the from of mail" + ) parser.add_argument( "-u", "--subject", dest="subject", action="store_true", - help="Print the subject of mail") + help="Print the subject of mail", + ) parser.add_argument( "-c", "--receiveds", dest="receiveds", action="store_true", - help="Print all receiveds of mail") + help="Print all receiveds of mail", + ) parser.add_argument( "-d", "--defects", dest="defects", action="store_true", - help="Print the defects of mail") + help="Print the defects of mail", + ) parser.add_argument( "-o", "--outlook", dest="outlook", action="store_true", - help="Analyze Outlook msg") + help="Analyze Outlook msg", + ) parser.add_argument( "-i", "--senderip", dest="senderip", metavar="Trust mail server string", - help="Extract a reliable sender IP address heuristically") + help="Extract a reliable sender IP address heuristically", + ) parser.add_argument( "-p", "--mail-hash", dest="mail_hash", action="store_true", - help="Print mail fingerprints without headers") + help="Print mail fingerprints without headers", + ) parser.add_argument( "-z", "--attachments-hash", dest="attachments_hash", action="store_true", - help="Print attachments with fingerprints") + help="Print attachments with fingerprints", + ) parser.add_argument( "-sa", "--store-attachments", dest="store_attachments", action="store_true", - help="Store attachments on disk") + help="Store attachments on disk", + ) parser.add_argument( "-ap", "--attachments-path", dest="attachments_path", default="/tmp", - help="Path where store attachments") + help="Path where store attachments", + ) parser.add_argument( - '-v', - '--version', - action='version', - version='%(prog)s {}'.format(__version__)) + "-v", "--version", action="version", version="%(prog)s {}".format(__version__) + ) return parser @@ -206,8 +202,7 @@ def main(): parser = mailparser.parse_from_string(args.string) elif args.stdin: if args.outlook: - raise MailParserOutlookError( - "You can't use stdin with msg Outlook") + raise MailParserOutlookError("You can't use stdin with msg Outlook") parser = mailparser.parse_from_file_obj(sys.stdin) if args.json: @@ -260,5 +255,5 @@ def main(): write_attachments(parser.attachments, args.attachments_path) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/mailparser/const.py b/src/mailparser/const.py index b8ea459..2b223f5 100644 --- a/src/mailparser/const.py +++ b/src/mailparser/const.py @@ -22,87 +22,77 @@ REGXIP = re.compile(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}") -JUNK_PATTERN = r'[ \(\)\[\]\t\n]+' +JUNK_PATTERN = r"[ \(\)\[\]\t\n]+" # Patterns for receiveds RECEIVED_PATTERNS = [ # each pattern handles matching a single clause - # need to exclude withs followed by cipher (e.g., google); (?! cipher) # TODO: ideally would do negative matching for with in parens - # need the beginning or space to differentiate from envelope-from ( - r'(?:(?:^|\s)from\s+(?P.+?)(?:\s*[(]?' - r'envelope-from|\s*[(]?envelope-sender|\s+' - r'by|\s+with(?! cipher)|\s+id|\s+for|\s+via|;))' + r"(?:(?:^|\s)from\s+(?P.+?)(?:\s*[(]?" + r"envelope-from|\s*[(]?envelope-sender|\s+" + r"by|\s+with(?! cipher)|\s+id|\s+for|\s+via|;))" ), - # need to make sure envelope-from comes before from to prevent mismatches # envelope-from and -sender seem to optionally have space and/or # ( before them other clauses must have whitespace before ( - r'(?:[^-]by\s+(?P.+?)(?:\s*[(]?envelope-from|\s*' - r'[(]?envelope-sender|\s+from|\s+with' - r'(?! cipher)|\s+id|\s+for|\s+via|;))' + r"(?:[^-]by\s+(?P.+?)(?:\s*[(]?envelope-from|\s*" + r"[(]?envelope-sender|\s+from|\s+with" + r"(?! cipher)|\s+id|\s+for|\s+via|;))" ), ( - r'(?:with(?! cipher)\s+(?P.+?)(?:\s*[(]?envelope-from|\s*[(]?' - r'envelope-sender|\s+from|\s+by|\s+id|\s+for|\s+via|;))' + r"(?:with(?! cipher)\s+(?P.+?)(?:\s*[(]?envelope-from|\s*[(]?" + r"envelope-sender|\s+from|\s+by|\s+id|\s+for|\s+via|;))" ), ( - r'[^\w](?:id\s+(?P.+?)(?:\s*[(]?envelope-from|\s*' - r'[(]?envelope-sender|\s+from|\s+by|\s+with' - r'(?! cipher)|\s+for|\s+via|;))' + r"[^\w](?:id\s+(?P.+?)(?:\s*[(]?envelope-from|\s*" + r"[(]?envelope-sender|\s+from|\s+by|\s+with" + r"(?! cipher)|\s+for|\s+via|;))" ), ( - r'(?:for\s+(?P.+?)(?:\s*[(]?envelope-from|\s*[(]?' - r'envelope-sender|\s+from|\s+by|\s+with' - r'(?! cipher)|\s+id|\s+via|;))' + r"(?:for\s+(?P.+?)(?:\s*[(]?envelope-from|\s*[(]?" + r"envelope-sender|\s+from|\s+by|\s+with" + r"(?! cipher)|\s+id|\s+via|;))" ), ( - r'(?:via\s+(?P.+?)(?:\s*[(]?' - r'envelope-from|\s*[(]?envelope-sender|\s+' - r'from|\s+by|\s+id|\s+for|\s+with(?! cipher)|;))' + r"(?:via\s+(?P.+?)(?:\s*[(]?" + r"envelope-from|\s*[(]?envelope-sender|\s+" + r"from|\s+by|\s+id|\s+for|\s+with(?! cipher)|;))" ), # assumes emails are always inside <> - r'(?:envelope-from\s+<(?P.+?)>)', - r'(?:envelope-sender\s+<(?P.+?)>)', - + r"(?:envelope-from\s+<(?P.+?)>)", + r"(?:envelope-sender\s+<(?P.+?)>)", # datetime comes after ; at the end - r';\s*(?P.*)', - + r";\s*(?P.*)", # sendgrid datetime ( - r'(?P\d{4}-\d{2}-\d{2} \d{2}:\d{2}:' - r'\d{2}\.\d{9} \+0000 UTC) m=\+\d+\.\d+' - ) + r"(?P\d{4}-\d{2}-\d{2} \d{2}:\d{2}:" + r"\d{2}\.\d{9} \+0000 UTC) m=\+\d+\.\d+" + ), ] -RECEIVED_COMPILED_LIST = [ - re.compile(i, re.I | re.DOTALL) for i in RECEIVED_PATTERNS] +RECEIVED_COMPILED_LIST = [re.compile(i, re.I | re.DOTALL) for i in RECEIVED_PATTERNS] EPILOGUE_DEFECTS = {"StartBoundaryNotFoundDefect"} -ADDRESSES_HEADERS = set([ - "bcc", - "cc", - "delivered-to", - "from", - "reply-to", - "to"]) +ADDRESSES_HEADERS = set(["bcc", "cc", "delivered-to", "from", "reply-to", "to"]) # These parts are always returned -OTHERS_PARTS = set([ - "attachments", - "body", - "date", - "message-id", - "received", - "subject", - "timezone", - "to_domains", - "user-agent", - "x-mailer", - "x-original-to", -]) +OTHERS_PARTS = set( + [ + "attachments", + "body", + "date", + "message-id", + "received", + "subject", + "timezone", + "to_domains", + "user-agent", + "x-mailer", + "x-original-to", + ] +) diff --git a/src/mailparser/exceptions.py b/src/mailparser/exceptions.py index c55a945..145f18e 100644 --- a/src/mailparser/exceptions.py +++ b/src/mailparser/exceptions.py @@ -17,13 +17,12 @@ limitations under the License. """ - __all__ = ( "MailParserError", "MailParserOutlookError", "MailParserEnvironmentError", "MailParserOSError", - "MailParserReceivedParsingError" + "MailParserReceivedParsingError", ) @@ -31,6 +30,7 @@ class MailParserError(Exception): """ Base MailParser Exception """ + pass @@ -38,6 +38,7 @@ class MailParserOutlookError(MailParserError): """ Raised when there is an error with Outlook integration """ + pass @@ -45,6 +46,7 @@ class MailParserEnvironmentError(MailParserError): """ Raised when the environment is not correct """ + pass @@ -52,6 +54,7 @@ class MailParserOSError(MailParserError): """ Raised when there is an OS error """ + pass @@ -59,4 +62,5 @@ class MailParserReceivedParsingError(MailParserError): """ Raised when a received header cannot be parsed """ + pass diff --git a/src/mailparser/mailparser.py b/src/mailparser/mailparser.py index fde35dc..5a3e174 100644 --- a/src/mailparser/mailparser.py +++ b/src/mailparser/mailparser.py @@ -27,10 +27,7 @@ import six import json -from .const import ( - ADDRESSES_HEADERS, - EPILOGUE_DEFECTS, - REGXIP) +from .const import ADDRESSES_HEADERS, EPILOGUE_DEFECTS, REGXIP from .utils import ( convert_mail_date, @@ -133,8 +130,7 @@ def __init__(self, message=None): Init a new object from a message object structure. """ self._message = message - log.debug( - "All headers of emails: {}".format(", ".join(message.keys()))) + log.debug("All headers of emails: {}".format(", ".join(message.keys()))) self.parse() def __str__(self): @@ -236,7 +232,8 @@ def from_bytes(cls, bt): log.debug("Parsing email from bytes") if six.PY2: raise MailParserEnvironmentError( - "Parsing from bytes is valid only for Python 3.x version") + "Parsing from bytes is valid only for Python 3.x version" + ) message = email.message_from_bytes(bt) return cls(message) @@ -338,7 +335,8 @@ def parse(self): epilogue = find_between( self.message.epilogue, "{}".format("--" + self.message.get_boundary()), - "{}".format("--" + self.message.get_boundary() + "--")) + "{}".format("--" + self.message.get_boundary() + "--"), + ) try: p = email.message_from_string(epilogue) @@ -350,30 +348,31 @@ def parse(self): # walk all mail parts for i, p in enumerate(parts): - if not p.is_multipart() or ported_string(p.get_content_disposition()).lower() == 'attachment': - charset = p.get_content_charset('utf-8') + if ( + not p.is_multipart() + or ported_string(p.get_content_disposition()).lower() == "attachment" + ): + charset = p.get_content_charset("utf-8") charset_raw = p.get_content_charset() log.debug("Charset {!r} part {!r}".format(charset, i)) - content_disposition = ported_string( - p.get('content-disposition')) - log.debug("content-disposition {!r} part {!r}".format( - content_disposition, i)) - content_id = ported_string(p.get('content-id')) - log.debug("content-id {!r} part {!r}".format( - content_id, i)) + content_disposition = ported_string(p.get("content-disposition")) + log.debug( + "content-disposition {!r} part {!r}".format(content_disposition, i) + ) + content_id = ported_string(p.get("content-id")) + log.debug("content-id {!r} part {!r}".format(content_id, i)) content_subtype = ported_string(p.get_content_subtype()) - log.debug("content subtype {!r} part {!r}".format( - content_subtype, i)) + log.debug("content subtype {!r} part {!r}".format(content_subtype, i)) filename = decode_header_part(p.get_filename()) is_attachment = False if filename: is_attachment = True else: - if content_id and content_subtype not in ('html', 'plain'): + if content_id and content_subtype not in ("html", "plain"): is_attachment = True filename = content_id - elif content_subtype in ('rtf'): + elif content_subtype in ("rtf"): is_attachment = True filename = "{}.rtf".format(random_string()) @@ -383,54 +382,72 @@ def parse(self): log.debug("Filename {!r} part {!r}".format(filename, i)) binary = False mail_content_type = ported_string(p.get_content_type()) - log.debug("Mail content type {!r} part {!r}".format( - mail_content_type, i)) + log.debug( + "Mail content type {!r} part {!r}".format(mail_content_type, i) + ) transfer_encoding = ported_string( - p.get('content-transfer-encoding', '')).lower() - log.debug("Transfer encoding {!r} part {!r}".format( - transfer_encoding, i)) - content_disposition = ported_string( - p.get('content-disposition')) - log.debug("content-disposition {!r} part {!r}".format( - content_disposition, i)) + p.get("content-transfer-encoding", "") + ).lower() + log.debug( + "Transfer encoding {!r} part {!r}".format(transfer_encoding, i) + ) + content_disposition = ported_string(p.get("content-disposition")) + log.debug( + "content-disposition {!r} part {!r}".format( + content_disposition, i + ) + ) if p.is_multipart(): - payload = ''.join([m.as_string() for m in p.get_payload(decode=False)]) + payload = "".join( + [m.as_string() for m in p.get_payload(decode=False)] + ) binary = False - log.debug("Filename {!r} part {!r} is multipart".format( - filename, i)) + log.debug( + "Filename {!r} part {!r} is multipart".format(filename, i) + ) elif transfer_encoding == "base64" or ( - transfer_encoding == "quoted-\ - printable" and "application" in mail_content_type): - + transfer_encoding + == "quoted-\ + printable" + and "application" in mail_content_type + ): payload = p.get_payload(decode=False) binary = True - log.debug("Filename {!r} part {!r} is binary".format( - filename, i)) + log.debug( + "Filename {!r} part {!r} is binary".format(filename, i) + ) elif "uuencode" in transfer_encoding: # Re-encode in base64 - payload = base64.b64encode( - p.get_payload(decode=True)).decode('ascii') + payload = base64.b64encode(p.get_payload(decode=True)).decode( + "ascii" + ) binary = True transfer_encoding = "base64" - log.debug("Filename {!r} part {!r} is binary (uuencode" - " re-encoded to base64)".format(filename, i)) + log.debug( + "Filename {!r} part {!r} is binary (uuencode" + " re-encoded to base64)".format(filename, i) + ) else: payload = ported_string( - p.get_payload(decode=True), encoding=charset) + p.get_payload(decode=True), encoding=charset + ) log.debug( - "Filename {!r} part {!r} is not binary".format( - filename, i)) - - self._attachments.append({ - "filename": filename, - "payload": payload, - "binary": binary, - "mail_content_type": mail_content_type, - "content-id": content_id, - "content-disposition": content_disposition, - "charset": charset_raw, - "content_transfer_encoding": transfer_encoding}) + "Filename {!r} part {!r} is not binary".format(filename, i) + ) + + self._attachments.append( + { + "filename": filename, + "payload": payload, + "binary": binary, + "mail_content_type": mail_content_type, + "content-id": content_id, + "content-disposition": content_disposition, + "charset": charset_raw, + "content_transfer_encoding": transfer_encoding, + } + ) # this isn't an attachments else: @@ -444,23 +461,25 @@ def parse(self): # we need to decode them with encoding python is appying # To maintain the characters payload = p.get_payload(decode=True) - cte = p.get('Content-Transfer-Encoding') + cte = p.get("Content-Transfer-Encoding") if cte: cte = cte.lower() - if not cte or cte in ['7bit', '8bit']: - payload = payload.decode('raw-unicode-escape') + if not cte or cte in ["7bit", "8bit"]: + payload = payload.decode("raw-unicode-escape") else: payload = ported_string(payload, encoding=charset) if payload: - if p.get_content_subtype() == 'html': + if p.get_content_subtype() == "html": self._text_html.append(payload) - elif p.get_content_subtype() == 'plain': + elif p.get_content_subtype() == "plain": self._text_plain.append(payload) else: log.warning( - 'Email content {!r} not handled'.format( - p.get_content_subtype())) + "Email content {!r} not handled".format( + p.get_content_subtype() + ) + ) self._text_not_managed.append(payload) else: # Parsed object mail with all parts @@ -507,13 +526,12 @@ def get_server_ipaddress(self, trust): i = ported_string(i) if trust in i: log.debug("Trust string {!r} is in {!r}".format(trust, i)) - check = REGXIP.findall(i[0:i.find("by")]) + check = REGXIP.findall(i[0 : i.find("by")]) if check: try: ip_str = six.text_type(check[-1]) - log.debug("Found sender IP {!r} in {!r}".format( - ip_str, i)) + log.debug("Found sender IP {!r} in {!r}".format(ip_str, i)) ip = ipaddress.ip_address(ip_str) except ValueError: return @@ -523,14 +541,12 @@ def get_server_ipaddress(self, trust): return ip_str def write_attachments(self, base_path): - """ This method writes the attachments of mail on disk + """This method writes the attachments of mail on disk Arguments: base_path {str} -- Base path where write the attachments """ - write_attachments( - attachments=self.attachments, - base_path=base_path) + write_attachments(attachments=self.attachments, base_path=base_path) def __getattr__(self, name): name = name.strip("_").lower() @@ -549,8 +565,7 @@ def __getattr__(self, name): # object headers elif name_header in ADDRESSES_HEADERS: - h = decode_header_part(self.message.get( - name_header, six.text_type())) + h = decode_header_part(self.message.get(name_header, six.text_type())) return email.utils.getaddresses([h]) # others headers @@ -596,7 +611,8 @@ def body(self): "--- mail_boundary ---" """ return "\n--- mail_boundary ---\n".join( - self.text_plain + self.text_html + self.text_not_managed) + self.text_plain + self.text_html + self.text_not_managed + ) @property def headers(self): @@ -641,7 +657,7 @@ def date(self): """ Return the mail date in datetime.datetime format and UTC. """ - date = self.message.get('date') + date = self.message.get("date") conv = None try: @@ -654,7 +670,7 @@ def timezone(self): """ Return timezone. Offset from UTC. """ - date = self.message.get('date') + date = self.message.get("date") timezone = 0 try: diff --git a/src/mailparser/utils.py b/src/mailparser/utils.py index b2269d5..7f84838 100644 --- a/src/mailparser/utils.py +++ b/src/mailparser/utils.py @@ -41,11 +41,7 @@ import six -from .const import ( - ADDRESSES_HEADERS, - JUNK_PATTERN, - OTHERS_PARTS, - RECEIVED_COMPILED_LIST) +from .const import ADDRESSES_HEADERS, JUNK_PATTERN, OTHERS_PARTS, RECEIVED_COMPILED_LIST from .exceptions import MailParserOSError, MailParserReceivedParsingError @@ -66,23 +62,25 @@ def custom_log(level="WARNING", name=None): # pragma: no cover "%(module)s | " "%(funcName)s | " "%(levelname)s | " - "%(message)s") + "%(message)s" + ) ch.setFormatter(formatter) log.addHandler(ch) return log def sanitize(func): - """ NFC is the normalization form recommended by W3C. """ + """NFC is the normalization form recommended by W3C.""" @functools.wraps(func) def wrapper(*args, **kwargs): - return normalize('NFC', func(*args, **kwargs)) + return normalize("NFC", func(*args, **kwargs)) + return wrapper @sanitize -def ported_string(raw_data, encoding='utf-8', errors='ignore'): +def ported_string(raw_data, encoding="utf-8", errors="ignore"): """ Give as input raw data and output a str in Python 3 and unicode in Python 2. @@ -133,8 +131,8 @@ def decode_header_part(header): try: for d, c in decode_header(header): - c = c if c else 'utf-8' - output += ported_string(d, c, 'ignore') + c = c if c else "utf-8" + output += ported_string(d, c, "ignore") # Header parsing failed, when header has charset Shift_JIS except (HeaderParseError, UnicodeError): @@ -148,7 +146,7 @@ def ported_open(file_): if six.PY2: return open(file_) elif six.PY3: - return open(file_, encoding="utf-8", errors='ignore') + return open(file_, encoding="utf-8", errors="ignore") def find_between(text, first_token, last_token): @@ -171,7 +169,7 @@ def fingerprints(data): namedtuple: fingerprints md5, sha1, sha256, sha512 """ - Hashes = namedtuple('Hashes', "md5 sha1 sha256 sha512") + Hashes = namedtuple("Hashes", "md5 sha1 sha256 sha512") if six.PY2: if not isinstance(data, str): @@ -223,12 +221,18 @@ def msgconvert(email): if six.PY2: with open(os.devnull, "w") as devnull: out = subprocess.Popen( - command, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=devnull) + command, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=devnull, + ) elif six.PY3: out = subprocess.Popen( - command, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + command, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + ) except OSError as e: message = "Check if 'msgconvert' tool is installed / {!r}".format(e) @@ -265,20 +269,17 @@ def parse_received(received): if len(matches) == 0: # no matches for this clause, but it's ok! keep going! - log.debug("No matches found for %s in %s" % ( - pattern.pattern, received)) + log.debug("No matches found for %s in %s" % (pattern.pattern, received)) continue elif len(matches) > 1: # uh, can't have more than one of each clause in a received. # so either there's more than one or the current regex is wrong - msg = "More than one match found for %s in %s" % ( - pattern.pattern, received) + msg = "More than one match found for %s in %s" % (pattern.pattern, received) log.error(msg) raise MailParserReceivedParsingError(msg) else: # otherwise we have one matching clause! - log.debug("Found one match for %s in %s" % ( - pattern.pattern, received)) + log.debug("Found one match for %s in %s" % (pattern.pattern, received)) match = matches[0].groupdict() if six.PY2: values_by_clause[match.keys()[0]] = match.values()[0] @@ -335,19 +336,21 @@ def receiveds_parsing(receiveds): values_by_clause = parse_received(received) except MailParserReceivedParsingError: # if we can't, let's append the raw - parsed.append({'raw': received}) + parsed.append({"raw": received}) else: # otherwise append the full values_by_clause dict parsed.append(values_by_clause) - log.debug("len(receiveds) %s, len(parsed) %s" % ( - len(receiveds), len(parsed))) + log.debug("len(receiveds) %s, len(parsed) %s" % (len(receiveds), len(parsed))) if len(receiveds) != len(parsed): # something really bad happened, # so just return raw receiveds with hop indices - log.error("len(receiveds): %s, len(parsed): %s, receiveds: %s, \ - parsed: %s" % (len(receiveds), len(parsed), receiveds, parsed)) + log.error( + "len(receiveds): %s, len(parsed): %s, receiveds: %s, \ + parsed: %s" + % (len(receiveds), len(parsed), receiveds, parsed) + ) return receiveds_not_parsed(receiveds) else: @@ -518,7 +521,7 @@ def safe_print(data): # pragma: no cover try: print(data) except UnicodeEncodeError: - print(data.encode('utf-8')) + print(data.encode("utf-8")) def print_mail_fingerprints(data): # pragma: no cover @@ -537,8 +540,7 @@ def print_attachments(attachments, flag_hash): # pragma: no cover else: payload = i["payload"] - i["md5"], i["sha1"], i["sha256"], i["sha512"] = \ - fingerprints(payload) + i["md5"], i["sha1"], i["sha256"], i["sha512"] = fingerprints(payload) for i in attachments: safe_print(json.dumps(i, ensure_ascii=False, indent=4)) @@ -578,7 +580,7 @@ def write_sample(binary, payload, path, filename): # pragma: no cover def random_string(string_length=10): - """ Generate a random string of fixed length + """Generate a random string of fixed length Keyword Arguments: string_length {int} -- String length (default: {10}) @@ -587,4 +589,4 @@ def random_string(string_length=10): str -- Random string """ letters = string.ascii_lowercase - return ''.join(random.choice(letters) for i in range(string_length)) + return "".join(random.choice(letters) for i in range(string_length)) diff --git a/tests/test_mail_parser.py b/tests/test_mail_parser.py index 82ce94b..c85deef 100644 --- a/tests/test_mail_parser.py +++ b/tests/test_mail_parser.py @@ -41,37 +41,34 @@ parse_received, random_string, ) -from mailparser.exceptions import MailParserEnvironmentError - +# base paths base_path = os.path.realpath(os.path.dirname(__file__)) -root = os.path.join(base_path, '..') - - - -mail_test_1 = os.path.join(base_path, 'mails', 'mail_test_1') -mail_test_2 = os.path.join(base_path, 'mails', 'mail_test_2') -mail_test_3 = os.path.join(base_path, 'mails', 'mail_test_3') -mail_test_4 = os.path.join(base_path, 'mails', 'mail_test_4') -mail_test_5 = os.path.join(base_path, 'mails', 'mail_test_5') -mail_test_6 = os.path.join(base_path, 'mails', 'mail_test_6') -mail_test_7 = os.path.join(base_path, 'mails', 'mail_test_7') -mail_test_8 = os.path.join(base_path, 'mails', 'mail_test_8') -mail_test_9 = os.path.join(base_path, 'mails', 'mail_test_9') -mail_test_10 = os.path.join(base_path, 'mails', 'mail_test_10') -mail_test_11 = os.path.join(base_path, 'mails', 'mail_test_11') -mail_test_12 = os.path.join(base_path, 'mails', 'mail_test_12') -mail_test_13 = os.path.join(base_path, 'mails', 'mail_test_13') -mail_test_14 = os.path.join(base_path, 'mails', 'mail_test_14') -mail_test_15 = os.path.join(base_path, 'mails', 'mail_test_15') -mail_malformed_1 = os.path.join(base_path, 'mails', 'mail_malformed_1') -mail_malformed_2 = os.path.join(base_path, 'mails', 'mail_malformed_2') -mail_malformed_3 = os.path.join(base_path, 'mails', 'mail_malformed_3') -mail_outlook_1 = os.path.join(base_path, 'mails', 'mail_outlook_1') +root = os.path.join(base_path, "..") + +# raw mails to test +mail_test_1 = os.path.join(base_path, "mails", "mail_test_1") +mail_test_2 = os.path.join(base_path, "mails", "mail_test_2") +mail_test_3 = os.path.join(base_path, "mails", "mail_test_3") +mail_test_4 = os.path.join(base_path, "mails", "mail_test_4") +mail_test_5 = os.path.join(base_path, "mails", "mail_test_5") +mail_test_6 = os.path.join(base_path, "mails", "mail_test_6") +mail_test_7 = os.path.join(base_path, "mails", "mail_test_7") +mail_test_8 = os.path.join(base_path, "mails", "mail_test_8") +mail_test_9 = os.path.join(base_path, "mails", "mail_test_9") +mail_test_10 = os.path.join(base_path, "mails", "mail_test_10") +mail_test_11 = os.path.join(base_path, "mails", "mail_test_11") +mail_test_12 = os.path.join(base_path, "mails", "mail_test_12") +mail_test_13 = os.path.join(base_path, "mails", "mail_test_13") +mail_test_14 = os.path.join(base_path, "mails", "mail_test_14") +mail_test_15 = os.path.join(base_path, "mails", "mail_test_15") +mail_malformed_1 = os.path.join(base_path, "mails", "mail_malformed_1") +mail_malformed_2 = os.path.join(base_path, "mails", "mail_malformed_2") +mail_malformed_3 = os.path.join(base_path, "mails", "mail_malformed_3") +mail_outlook_1 = os.path.join(base_path, "mails", "mail_outlook_1") class TestMailParser(unittest.TestCase): - def setUp(self): self.all_mails = ( mail_test_1, @@ -89,14 +86,16 @@ def setUp(self): mail_test_13, mail_malformed_1, mail_malformed_2, - mail_malformed_3) + mail_malformed_3, + ) def test_write_attachments(self): attachments = [ "<_1_0B4E44A80B15F6FC005C1243C12580DD>", "<_1_0B4E420C0B4E3DD0005C1243C12580DD>", "<_1_0B4E24640B4E1564005C1243C12580DD>", - "Move To Eight ZWEP6227F.pdf"] + "Move To Eight ZWEP6227F.pdf", + ] random_path = os.path.join(root, "tests", random_string()) mail = mailparser.parse_from_file(mail_test_10) os.makedirs(random_path) @@ -197,16 +196,22 @@ def test_ipaddress_unicodeerror(self): def test_fingerprints_body(self): mail = mailparser.parse_from_file(mail_test_1) - md5, sha1, sha256, sha512 = fingerprints( - mail.body.encode("utf-8")) + md5, sha1, sha256, sha512 = fingerprints(mail.body.encode("utf-8")) self.assertEqual(md5, "55852a2efe95e7249887c92cc02123f8") self.assertEqual(sha1, "62fef1e38327ed09363624c3aff8ea11723ee05f") - self.assertEqual(sha256, ("cd4af1017f2e623f6d38f691048b6" - "a28d8b1f44a0478137b4337eac6de78f71a")) - self.assertEqual(sha512, ("4a573c7929b078f2a2c1c0f869d418b0c020d4" - "d37196bd6dcc209f9ccb29ca67355aa5e47b97" - "c8bf90377204f59efde7ba1fc071b6f250a665" - "72f63b997e92e8")) + self.assertEqual( + sha256, + ("cd4af1017f2e623f6d38f691048b6" "a28d8b1f44a0478137b4337eac6de78f71a"), + ) + self.assertEqual( + sha512, + ( + "4a573c7929b078f2a2c1c0f869d418b0c020d4" + "d37196bd6dcc209f9ccb29ca67355aa5e47b97" + "c8bf90377204f59efde7ba1fc071b6f250a665" + "72f63b997e92e8" + ), + ) def test_fingerprints_unicodeencodeerror(self): mail = mailparser.parse_from_file(mail_test_7) @@ -220,7 +225,7 @@ def test_malformed_mail(self): self.assertIn("MultipartInvariantViolationDefect", defects_categories) self.assertIn("reply-to", mail.mail) self.assertNotIn("reply_to", mail.mail) - reply_to = [(u'VICTORIA Souvenirs', u'smgesi4@gmail.com')] + reply_to = [("VICTORIA Souvenirs", "smgesi4@gmail.com")] self.assertEqual(mail.reply_to, reply_to) self.assertEqual(mail.fake_header, six.text_type()) @@ -362,8 +367,7 @@ def test_defects(self): self.assertEqual(1, len(mail.defects)) self.assertEqual(1, len(mail.defects_categories)) self.assertIn("defects", mail.mail) - self.assertIn("StartBoundaryNotFoundDefect", - mail.defects_categories) + self.assertIn("StartBoundaryNotFoundDefect", mail.defects_categories) self.assertIsInstance(mail.mail_json, six.text_type) result = len(mail.attachments) @@ -378,8 +382,7 @@ def test_defects(self): self.assertEqual(1, len(mail.defects)) self.assertEqual(1, len(mail.defects_categories)) self.assertIn("defects", mail.mail) - self.assertIn( - "CloseBoundaryNotFoundDefect", mail.defects_categories) + self.assertIn("CloseBoundaryNotFoundDefect", mail.defects_categories) @unittest.skip("Skipping this test for now") def test_defects_bug(self): @@ -389,8 +392,7 @@ def test_defects_bug(self): self.assertEqual(1, len(mail.defects)) self.assertEqual(1, len(mail.defects_categories)) self.assertIn("defects", mail.mail) - self.assertIn("StartBoundaryNotFoundDefect", - mail.defects_categories) + self.assertIn("StartBoundaryNotFoundDefect", mail.defects_categories) self.assertIsInstance(mail.parsed_mail_json, six.text_type) result = len(mail.attachments) @@ -405,18 +407,15 @@ def test_add_content_type(self): self.assertEqual(len(result["attachments"]), 1) self.assertIsInstance( - result["attachments"][0]["mail_content_type"], six.text_type) + result["attachments"][0]["mail_content_type"], six.text_type + ) self.assertFalse(result["attachments"][0]["binary"]) - self.assertIsInstance( - result["attachments"][0]["payload"], six.text_type) - self.assertEqual( - result["attachments"][0]["content_transfer_encoding"], - "quoted-printable") - self.assertEqual( - result["attachments"][0]["charset"], - "iso-8859-1") + self.assertIsInstance(result["attachments"][0]["payload"], six.text_type) self.assertEqual( - result["attachments"][0]["content-disposition"], "inline") + result["attachments"][0]["content_transfer_encoding"], "quoted-printable" + ) + self.assertEqual(result["attachments"][0]["charset"], "iso-8859-1") + self.assertEqual(result["attachments"][0]["content-disposition"], "inline") mail = mailparser.parse_from_file(mail_malformed_1) attachments = mail.mail["attachments"] @@ -562,12 +561,12 @@ def test_ported_string(self): s = ported_string(raw_data) self.assertEqual(s, six.text_type()) - raw_data = u"test" + raw_data = "test" s = ported_string(raw_data) self.assertEqual(s, "test") def test_standard_outlook(self): - """ Verify a basic outlook received header works. """ + """Verify a basic outlook received header works.""" received = """ from DM3NAM03FT035 by CY4PR0601CA0051.outlook.office365.com @@ -577,19 +576,19 @@ def test_standard_outlook(self): """.strip() expected = { - 'from': 'DM3NAM03FT035', - 'by': 'CY4PR0601CA0051.outlook.office365.com', - 'with': 'Microsoft SMTP Server version=TLS1_2, cipher=TLS', - 'id': '15.20.1185.23', - 'via': 'Frontend Transport', - 'date': 'Mon, 1 Oct 2018 09:49:21 +0000' + "from": "DM3NAM03FT035", + "by": "CY4PR0601CA0051.outlook.office365.com", + "with": "Microsoft SMTP Server version=TLS1_2, cipher=TLS", + "id": "15.20.1185.23", + "via": "Frontend Transport", + "date": "Mon, 1 Oct 2018 09:49:21 +0000", } values_by_clause = parse_received(received) self.assertEqual(expected, values_by_clause) def test_standard_google__with_cipher(self): - """ Verify that we don't match 'with cipher' a la google. """ + """Verify that we don't match 'with cipher' a la google.""" received = """ from mail_yw1_f65.google.com by subdomain.domain.com Postfix with ESMTPS @@ -597,12 +596,12 @@ def test_standard_google__with_cipher(self): Tue, 25 Sep 2018 13:09:36 +0000 (UTC)""" expected = { - 'from': 'mail_yw1_f65.google.com', - 'by': 'subdomain.domain.com Postfix', - 'with': 'ESMTPS', - 'id': 'abc123', - 'for': '', - 'date': 'Tue, 25 Sep 2018 13:09:36 +0000 (UTC)' + "from": "mail_yw1_f65.google.com", + "by": "subdomain.domain.com Postfix", + "with": "ESMTPS", + "id": "abc123", + "for": "", + "date": "Tue, 25 Sep 2018 13:09:36 +0000 (UTC)", } values_by_clause = parse_received(received) self.assertEqual(expected, values_by_clause) @@ -660,7 +659,7 @@ def test_write_uuencode_attachment(self): temp_dir = tempfile.mkdtemp() mail.write_attachments(temp_dir) md5 = hashlib.md5() - with open(os.path.join(temp_dir, 'REQUEST FOR QUOTE.zip'), 'rb') as f: + with open(os.path.join(temp_dir, "REQUEST FOR QUOTE.zip"), "rb") as f: md5.update(f.read()) shutil.rmtree(temp_dir) - self.assertEqual(md5.hexdigest(), '4f2cf891e7cfb349fca812091f184ecc') + self.assertEqual(md5.hexdigest(), "4f2cf891e7cfb349fca812091f184ecc") diff --git a/tests/test_main.py b/tests/test_main.py index a82a54e..e1f8ff2 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -17,7 +17,6 @@ limitations under the License. """ - import unittest @@ -25,7 +24,6 @@ class TestMain(unittest.TestCase): - def setUp(self): self.parser = get_args() @@ -73,15 +71,13 @@ def test_options(self): parsed = self.parser.parse_args(["--file", "mail.eml", "-d"]) self.assertTrue(parsed.defects) - parsed = self.parser.parse_args([ - "--file", "mail.eml", "--senderip", "trust"]) + parsed = self.parser.parse_args(["--file", "mail.eml", "--senderip", "trust"]) self.assertTrue(parsed.senderip) parsed = self.parser.parse_args(["--file", "mail.eml", "-p"]) self.assertTrue(parsed.mail_hash) - parsed = self.parser.parse_args([ - "--file", "mail.eml", "--attachments-hash"]) + parsed = self.parser.parse_args(["--file", "mail.eml", "--attachments-hash"]) self.assertTrue(parsed.attachments_hash) parsed = self.parser.parse_args(["--file", "mail.eml", "-c"]) From 467f14dc7d0186c9d9f4d48ebb3075952a040886 Mon Sep 17 00:00:00 2001 From: Fedele Mantuano Date: Thu, 24 Oct 2024 09:02:28 +0200 Subject: [PATCH 8/8] Minor change --- tests/test_mail_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_mail_parser.py b/tests/test_mail_parser.py index c85deef..6ce85bf 100644 --- a/tests/test_mail_parser.py +++ b/tests/test_mail_parser.py @@ -201,7 +201,7 @@ def test_fingerprints_body(self): self.assertEqual(sha1, "62fef1e38327ed09363624c3aff8ea11723ee05f") self.assertEqual( sha256, - ("cd4af1017f2e623f6d38f691048b6" "a28d8b1f44a0478137b4337eac6de78f71a"), + ("cd4af1017f2e623f6d38f691048b6a28d8b1f44a0478137b4337eac6de78f71a"), ) self.assertEqual( sha512,