diff options
Diffstat (limited to 'comm/mailnews/local/test')
57 files changed, 8243 insertions, 0 deletions
diff --git a/comm/mailnews/local/test/moz.build b/comm/mailnews/local/test/moz.build new file mode 100644 index 0000000000..6b37fdbe09 --- /dev/null +++ b/comm/mailnews/local/test/moz.build @@ -0,0 +1,6 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.ini"] diff --git a/comm/mailnews/local/test/unit/data/dot b/comm/mailnews/local/test/unit/data/dot new file mode 100644 index 0000000000..adab8ff515 --- /dev/null +++ b/comm/mailnews/local/test/unit/data/dot @@ -0,0 +1,10 @@ +From - Tue Jan 31 11:44:20 2012
+Subject: Dot at the line head
+Date: Tue, 21 Jan 2012 11:44:20 +0000
+Mime-Version: 1.0
+Content-Type: text/html; charset=iso-8859-1; format=flowed
+
+.
+. This is a line starting with a dot.
+.
+
diff --git a/comm/mailnews/local/test/unit/data/invalid_mozilla_keys.eml b/comm/mailnews/local/test/unit/data/invalid_mozilla_keys.eml new file mode 100644 index 0000000000..611029bb7d --- /dev/null +++ b/comm/mailnews/local/test/unit/data/invalid_mozilla_keys.eml @@ -0,0 +1,5 @@ +Date: Tue, 21 Jan 2012 11:44:20 +0000
+X-Mozilla-Keys:
+
+
+This mail has invalid X-Mozilla-Keys header.
diff --git a/comm/mailnews/local/test/unit/data/mailformed_recipients.eml b/comm/mailnews/local/test/unit/data/mailformed_recipients.eml new file mode 100644 index 0000000000..3a3b9a874a --- /dev/null +++ b/comm/mailnews/local/test/unit/data/mailformed_recipients.eml @@ -0,0 +1,66 @@ +Return-Path: <notifier.mars@krw.rzd>
+Received: from [10.95.185.198] (HELO mars)
+ by cgbe1.sf.icc.krsk.krw.rzd (CommuniGate Pro SMTP 5.4.2)
+ with ESMTP id 197561; Sat, 03 Dec 2011 17:07:41 +0400
+Subject: CS-MARS Incident Notification (red, Rule Name: System Rule: DoS: Network - Success Likely)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: 7bit
+Date: Sat, 3 Dec 2011 17:07:41 +0400
+From: notifier.mars@krw.rzd
+Message-Id: <1322917661.14@mars>
+To: LyukshinRA@krw.rzd,
+ biakus@krw.rzd,
+
+
+The following incident occurred on "mars"
+
+Start time: Sat Dec 3 16:52:33 2011
+End time: Sat Dec 3 17:07:35 2011
+Fired Rule Id: 3354883
+Fired Rule: System Rule: DoS: Network - Success Likely
+Incident Id: 24500896822
+Incident Severity:red
+
+Top 3 src-dest address pairs sorted by severity and count (showing 3 of 319):
+1. N/A -> 10.88.21.45 Severity: red Count: 16
+2. 10.89.234.223 -> N/A Severity: red Count: 16
+3. 10.144.58.124 -> 10.92.23.37 Severity: green Count: 1
+
+Top 3 src ip's address sorted by severity and count (showing 3 of 10):
+1. N/A -> Severity: red Count: 16
+2. 10.89.234.223 -> Severity: red Count: 16
+3. 10.132.51.53 -> Severity: green Count: 48
+
+Top 3 dest ip's address sorted by severity and count (showing 3 of 319):
+1. 10.88.21.45 -> Severity: red Count: 16
+2. N/A -> Severity: red Count: 16
+3. 10.92.23.37 -> Severity: green Count: 1
+
+Top 3 dest TCP/UDP ports sorted by severity and count (showing 0 of 0):
+
+Top 3 event types sorted by severity and count (showing 2 of 2):
+1. Sudden increase of traffic to a port Severity: red Count: 32
+2. Deny packet due to security policy Severity: green Count: 317
+
+Top 3 reporting devices sorted by count (showing 3 of 11):
+1. KRW-EXP3 Count: 152
+2. kzi-spd-asa.secadm.m.krw.rzd Count: 151
+3. mars Count: 32
+
+
+
+For more details about this incident please go to:
+ https://mars/Incidents/IncidentDetails.jsp?Incident_Id=24500896822
+ https://mars.secadm.m.krw.rzd/Incidents/IncidentDetails.jsp?Incident_Id=24500896822
+ https://mars.krw.rzd/Incidents/IncidentDetails.jsp?Incident_Id=24500896822
+ https://10.95.185.198/Incidents/IncidentDetails.jsp?Incident_Id=24500896822
+ https://10.95.185.130/Incidents/IncidentDetails.jsp?Incident_Id=24500896822
+
+For all incidents occurred recently please go to:
+ https://mars/Incidents/
+ https://mars.secadm.m.krw.rzd/Incidents/
+ https://mars.krw.rzd/Incidents/
+ https://10.95.185.198/Incidents/
+ https://10.95.185.130/Incidents/
+
diff --git a/comm/mailnews/local/test/unit/data/mailformed_subject.eml b/comm/mailnews/local/test/unit/data/mailformed_subject.eml new file mode 100644 index 0000000000..b4cae27827 --- /dev/null +++ b/comm/mailnews/local/test/unit/data/mailformed_subject.eml @@ -0,0 +1,1934 @@ +From - Wed Nov 09 20:53:04 2011
+X-Account-Key: account6
+X-UIDL: AK9oUtQAAQncTrq1IQTAI2gZvkw
+X-Mozilla-Status: 1003
+X-Mozilla-Status2: 00000000
+X-Mozilla-Keys:
+X-Apparently-To: milonguero66@yahoo.de via 212.82.104.175; Wed, 09 Nov 2011 17:15:13 +0000
+Received-SPF: none (domain of yahoo.de does not designate permitted sender hosts)
+X-YMailISG: ki6Cgm0WLDt5_yRciWKCGRrm_vnjeHjNnC0KO465U1eaOZyt
+ pivGwlOFQRjp2s2Ygsm5JQ9e3mXBbof5vgzmR3tyuYMCf7EIK7zbRrnlOVCi
+ KfWabfqU2cTV2h7Ic5RTSuENb5nJQIXwVJKAsZspB63KFOJ4tESpGHVHOXLl
+ DcjkJJPSCgA23jDgVh_2XoL__I9xRfVC93IJkNBx_k1iWhz7faHweH.AS26L
+ E2Z0o97aS7U.UR3JJPtTZYjhPyw5ncRZefyDpP6XXTHGjbXp7TkaopFfZ1t5
+ MO0QFlOSCAXq30VBg.ViDBIz6nfSetzflBHtgCRA2k6ovl.KLOPN4ZOvV3Io
+ YikmLnc3EMQKKwuGytzvr0qvB2W_dARwmdVAkWdpAFqoGQIRsuuK.vEPbQgt
+ o.4_7krO5H.E5yTSKs3AxRpSgu7Tpqo1USzeN6VfSp4XSsqoEXf6jPwo9COl
+ cRiDNC1ofVzcPDp1FP2A6ihTDsi64ZKhrtfRXBPSAWs0DbNsjrbO2qAG8iZi
+ cmO1Hxg_8F5BxOolKIkzc3ykR7Ou.M0ebP9OusURSxfgLLmUo4Mw2asmWCTZ
+ umthtzDW1Zf76E.flIaVKlP0btDZqJpvTVyg7KN0cWRMtFdC4ybvQLyVxSK2
+ KWQ7f232DAlH.JUbIuJKVnyuTVAlZBPoZue8AQi3P53J2ZwC7XB6VXahgqgn
+ xKlZiD.zUREwUeUCJQBMSZk7re7TrCoPwXfE.n3tc8NjtmIzeQRPxAPdl1Ka
+ 8fwMfibbBAAOS9SmeqayScRVPjvNyidH1t5BNk0YWc6EzyxgLPvru65hvXnZ
+ W3CayiMl7XKxeIDctxiRKlbUhJ_QzgVGMunN_jynWts_1vIIBvGUbaP9EFjZ
+ PAcNoNwSiTnSlQ3cc4aLCaGBUG.Gq8e5u3zUu_L0HRvAhIaZcBvi_xiWjDjp
+ kRXylog9074fulAzY_7Zlbq2.xa6AdUR7PrOzlS3QMblu5yc4_hlpq8KPYbM
+ voqzmi6JIZ8dAKTp4BAVFr7Q63UlHJWUjsdlJ1uIds.dl1OxJiTgGfEh.O1g
+ tyh3zY_hE3z7zRW7LFzEqoNclh8WYNie0j7kDHYfXbR1klg49y2mK_8jJRFA
+ nAOeUm71bWXPP65HaHm2r8ti.czc2g_CxzuReSyWyhdJNvLe5YTwpVZqCcbS
+ rhx7A7jtQiNf
+X-Originating-IP: [217.146.183.238]
+Authentication-Results: mta1087.mail.ird.yahoo.com from=yahoo.de; domainkeys=neutral (no sig); from=yahoo.de; dkim=pass (ok)
+Received: from 127.0.0.1 (HELO nm8-vm0.bullet.mail.ukl.yahoo.com) (217.146.183.238)
+ by mta1087.mail.ird.yahoo.com with SMTP; Wed, 09 Nov 2011 17:15:13 +0000
+Received: from [217.146.183.212] by nm8.bullet.mail.ukl.yahoo.com with NNFMP; 09 Nov 2011 17:15:12 -0000
+Received: from [77.238.184.80] by tm5.bullet.mail.ukl.yahoo.com with NNFMP; 09 Nov 2011 17:15:12 -0000
+Received: from [127.0.0.1] by smtp149.mail.ukl.yahoo.com with NNFMP; 09 Nov 2011 17:15:12 -0000
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo.de; s=s1024; t=1320858912; bh=HOGFslJAcWp5sxJ2b+wkpi2WeYP/qYljF1212byJva8=; h=X-Yahoo-Newman-Id:X-Yahoo-Newman-Property:X-YMail-OSG:X-Yahoo-SMTP:Received:MIME-Version:Message-Id:Date:Content-Type:X-Mailer:From:X-FID:X-Priority:To:Subject; b=lb3j0vTYp/7M5Mgxy6xy1mn0rkgwxme0XA3iGfZrWEiQ3i3VNFsspvEXyWjerEKVcggZZyvH3Wjgc5U8HxxG73FrdEHjunpYEMy7Vp0sA4L+s+pndQdqI710P3SaDP21+VSA8HhKO03X3k4CVy5od2iSUGqdDa/hcJwYPgPBhZM=
+X-Yahoo-Newman-Id: 289474.41256.bm@smtp149.mail.ukl.yahoo.com
+X-YMail-OSG: QnILw7kVM1ljfDVZBNFZdSZEHkFwvGT1RdUyd.LgwTYAaBt
+ YMNKhbRWIEnXuXKkEHGuj_Sfc8hluz9X9kIlgz9EsdAdrB_gcA85hCzCU3_v
+ gpoxkri1I9D_2rR79yzbCR7Pi6ltYwTnticz1f40ZsonLoDAsGloPD26p_sp
+ xjH2379iUBy5m2tdBG4LP7C64twSNJVSivUYEQakBGKdvA7lNOsTZl.Gseh.
+ Qr8bS65cSwyKFKsC8jQGsowP7r_flK.gD.YJf7BKKCxal.SIOjAjIwZiDF2g
+ TufXZcJKOguH.kUeolYYe1DfS1qJ1WHYCkUKXZ8eloDp5N4id8XsNsl.IYdp
+ dXjQ71ZvRf6x0vMHEO1ykyWT45MpmKClZzsa_up1GmYM.mJ0fYPawsVDKLPj
+ vLa67ErmwS71fflMajhNBi9KZaCxLLBrXNvL0dprlyLG6rIpm4h9EvihcZOq
+ dARgemVEvLAtwAM50Et4NRKktpDhYBUr97kp.7dN0xTOzvqKqU3.2rfTFJ42
+ Kil6kccIv3e3njoOaQ1y.GQt6e_WjvtaDSVHRB3wJDN_8b7SE3nKkhkfMx.R
+ .ZEmZ4ywN8zixGYBOJrzH
+X-Yahoo-SMTP: 6uAm6CCswBCasj3_KVt4BWWJsLuOj9UNkP4D
+Received: from milkyway (pink_amaryllis@89.204.137.94 with login)
+ by smtp149.mail.ukl.yahoo.com with SMTP; 09 Nov 2011 17:15:06 +0000 GMT
+MIME-Version: 1.0
+Message-Id: <4EBAB50C.00000E.03032@MILKYWAY>
+Date: Wed, 9 Nov 2011 18:14:52 +0100 (Westeuropäische Normalzeit)
+Content-Type: Multipart/related;
+ charset="iso-8859-1";
+ type="multipart/alternative";
+ boundary="------------Boundary-00=_S8LEXFP0000000000000"
+X-Mailer: IncrediMail (6274918)
+From: "Eva C. Hammel" <pink_amaryllis@yahoo.de>
+X-FID: 1D3F6781-6356-4D2B-9E2A-C2FC357A0AB3
+X-Priority: 3
+To: <milonguero66@yahoo.de>
+Subject: =?iso-8859-1?B?UmVjaG51bmcgQW535Gx0aW4=?=
+
+
+
+--------------Boundary-00=_S8LEXFP0000000000000
+Content-Type: Multipart/Alternative;
+ boundary="------------Boundary-00=_S8LESPT1VA4000000000"
+
+
+--------------Boundary-00=_S8LESPT1VA4000000000
+Content-Type: Text/Plain;
+ charset="iso-8859-1"
+Content-Transfer-Encoding: quoted-printable
+
+ist bearbeitet;=0D
+Ku=DF!
+--------------Boundary-00=_S8LESPT1VA4000000000
+Content-Type: Text/HTML;
+ charset="iso-8859-1"
+Content-Transfer-Encoding: quoted-printable
+
+<HTML><HEAD>
+<META content=3D"text/html; charset=3Diso-8859-1" http-equiv=3DContent-Ty=
+pe>
+<META name=3DGENERATOR content=3D"IncrediMail 1.0">
+<STYLE>=0Av\:* {behavior:url (#default#vml);}=0A</STYLE>
+
+<!--IncrdiXMLRemarkStart>
+<IncrdiX-Info>
+<X-FID>1D3F6781-6356-4D2B-9E2A-C2FC357A0AB3</X-FID>
+<X-FVER>4,000000</X-FVER>
+<X-FIT>Letter</X-FIT>
+<X-FILE>Letter\jack-o-lantern.imf</X-FILE>
+<X-FCOL>Halloween</X-FCOL>
+<X-FCAT>Holidays</X-FCAT>
+<X-FDIS>Jack-O-Lantern</X-FDIS>
+<X-Extensions>SU1CTDEsNDYsgUmBSYUolTiZncGFTZmVjZlNiSiRME3FLJEkTTSRODSVjZ0=
+kgSQwlUmBSYFJgSxJTUJMMiwwLCxJTUJMMywwLCw=3D</X-Extensions>
+<X-BG>cid:05946161-B6A4-43DD-9BA5-19BC9B252161</X-BG>
+<X-BGT>no-repeat</X-BGT>
+<X-BGC>#f35901</X-BGC>
+<X-BGPX>right</X-BGPX>
+<X-BGPY>bottom</X-BGPY>
+<X-ASN>BCEB29C0-42D3-11D4-BA3E-0050DAC68030</X-ASN>
+<X-ASNF>0</X-ASNF>
+<X-ASH>BCEB29C0-42D3-11D4-BA3E-0050DAC68030</X-ASH>
+<X-ASHF>1</X-ASHF>
+<X-AN>BFF138F0-3EFC-11D4-BA3D-0050DAC68030</X-AN>
+<X-ANF>0</X-ANF>
+<X-AP>BFF138F0-3EFC-11D4-BA3D-0050DAC68030</X-AP>
+<X-APF>1</X-APF>
+<X-AD>E3F15280-2BF7-11D4-BA28-0050DAC68030</X-AD>
+<X-ADF>0</X-ADF>
+<X-AUTO>X-ASN,X-ASH,X-AN,X-AP,X-AD</X-AUTO>
+<X-CNT>;</X-CNT>
+</IncrdiX-Info>
+<IncrdiXMLRemarkEnd-->
+</HEAD>
+<BODY style=3D"MARGIN: 0px 200px 0px 10px; BACKGROUND-REPEAT: no-repeat; =
+FONT-FAMILY: Verdana; BACKGROUND-POSITION: right bottom; COLOR: #f8fdb5; =
+FONT-SIZE: 12pt" background=3Dcid:05946161-B6A4-43DD-9BA5-19BC9B252161 aL=
+ink=3D#00ff00 scroll=3Dyes link=3D#00ff00 bgProperties=3Dfixed bgColor=3D=
+#f35901 text=3D#f8fdb5 vLink=3D#00ff00 INCREDIFIXEDFORIMOL=3D"true" SIGCO=
+LOR=3D"16777215">
+<TABLE id=3DINCREDIMAINTABLE border=3D0 cellSpacing=3D0 cellPadding=3D2 w=
+idth=3D"100%">
+<TBODY>
+<TR>
+<TD style=3D"POSITION: relative; DIRECTION: ltr; FONT-SIZE: 12pt" id=3DIN=
+CREDITEXTREGION vAlign=3Dtop width=3D"100%">
+<DIV style=3D"PADDING-LEFT: 2px; FONT-FAMILY: DejaVu Serif; FONT-SIZE: 12=
+pt" id=3DINCREDI_TEXT_AREA>
+<DIV>ist bearbeitet;</DIV>
+<DIV>Ku=DF!</DIV><BR><BR><BR></DIV></TD></TR>
+<TR>
+<TD id=3DINCREDIFOOTER width=3D"100%">
+<TABLE cellSpacing=3D0 cellPadding=3D0 width=3D"100%">
+<TBODY>
+<TR>
+<TD width=3D"100%"></TD>
+<TD id=3DINCREDISOUND vAlign=3Dbottom align=3Dmiddle></TD>
+<TD id=3DINCREDIANIM vAlign=3Dbottom align=3Dmiddle></TD></TR></TBODY></T=
+ABLE></TD></TR></TBODY></TABLE><SPAN id=3DIncrediStamp><A href=3D"http://=
+www.incredimail.com/?id=3D619278&did=3D10500&ppd=3D2690,201107041=
+701,7,[TypeID],[IM_UPN2]&rui=3D116597769&sd=3D20111109"><SPAN nam=
+e=3D"imgCache" border=3D"0"><IMG border=3D0 alt=3D"Tierisch gut! KOSTENLO=
+SE E-Mail-Animationen =96 von IncrediMail! Hier Klicken!" src=3D"cid:F0E3=
+0D5E-C01C-4CC4-BCED-F05ADA8739AC"></SPAN></A></SPAN></BODY></HTML>
+--------------Boundary-00=_S8LESPT1VA4000000000--
+
+--------------Boundary-00=_S8LEXFP0000000000000
+Content-Type: image/jpeg;
+ name="pum_final.jpg"
+Content-Transfer-Encoding: base64
+Content-ID: <05946161-B6A4-43DD-9BA5-19BC9B252161>
+
+/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAARgAA/+4ADkFkb2JlAGTAAAAAAf/b
+AIQABAMDAwMDBAMDBAYEAwQGBwUEBAUHCAYGBwYGCAoICQkJCQgKCgwMDAwMCgwMDQ0MDBERERER
+FBQUFBQUFBQUFAEEBQUIBwgPCgoPFA4ODhQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU
+FBQUFBQUFBQUFBQUFBQUFBQU/8AAEQgCeAXcAwERAAIRAQMRAf/EALoAAQEAAQUBAQAAAAAAAAAA
+AAABBgIDBAUHCAkBAQEAAgMBAQAAAAAAAAAAAAABAgQDBQYHCBAAAgIBAgQEBAQDBQgBAQkAABEB
+EgIDBCExQQVRYZEGcYETB6EiMhTwsfHhQiMVCMHRUmJyMyQWgpKiwkNTY3ODNCURAQEAAgECBAIH
+BgQFBAEEAwARAQIDIQQxQRIFUWHwcYGRoSITscHRMkIG4VIjFGJygpIV8aLSM0OywuIWU9Mk/9oA
+DAMBAAIRAxEAPwDSz84P1QMAwJ/IAwDAMAwABgGAfEAAAAGAYBwAYBgGAYCwgMAwFhAYB8QDAjKK
+yA+IgPxAjKKyCMoTMALCBYQVkEfAoMBMoAwDAMAwg48Qo+IB+ABgGAt4iAwgwowgwDClhBGEWwgW
+EUsIJYQLCBYQWMhBGAfHwCD6QBWBH4ALCKTlIiDgKWEQiQKwI5+YBgGFHwEQYBvqVUaCLYRRqQiW
+EVWEH5EEsWKTIFmZCDAjlAH1ANhRhBsKPxkIW8eYijYQfqFGELeMCA5kAwI/UKrCDAOAD6cwDEEY
+FbAjkKrmQg+oEfAC2EEsIDjqAYBgHIB9ADATIB+ZQZAbAMoPwIDKDID/ALQD6AGUHJAZQiQDQKMA
+wDkBE+oB+MgGBGAYCZkCvgBGgDARIFCVGFWJAWkREYBhRhB+YUYB+AKMAAAMAwDBR+AQ/iQo0AYS
+jAfiFoEGFqMChBgRhRgVhEYVWEGBrfmYqMoMA0QGII559CqMIrkgjKKwI+gFYBoCOPEA5AWEBgH5
+BR9AgwD4gLCA/kAYCJAjCqwgwJM9OgVWwhZiKj4hFYCJQEfgAYBgGBWBGAYgMAwD8ygyA4ATIBlB
+x1ID8SgwDYCMuogWJAZRGBfgCjAj8QKwDIDRQsIDYBgGwI1yAMA/QA+gB+YCZ+AFchEcoKNhBz4h
+RsACgFYEbAPxkAwDfxCD4AQKrkB/IIRIBvmAYUfiEGBGFVhEABRgVhBgRgGBWBGAYBgGAcfMorIV
+GUGQGUGAcAo0AYBgGAYKMFGAYBgGAYCwiDCjAMA+gBgGBGAYFYBgHwAjgA4AMAwDAMAwDAWECwgM
+AwDAMAwFhBGBbCAwDAMIMRUYBlBgGBWQRgGUGAYBgGEGFGELCAwowD8wFhEH5gGAYUYRGAYBgGBZ
+kKMIjkDWYslBRhEYFYKjArCowDCK+AEYUYQYBgGUo+BFVhEZQZAfoUGAZAZQYBkBlBkBlBgGAYWj
+CESAsIDAMA/UAwD9QDAWEEYFfgBGBbCCMAwDAWEBiBMgWwiIwowDAMAwDAMAwDYBgH4gGAfgBLCC
+sA2EGAYUYgMIlixSwiDCjIg/QAygwDClhAYQmQFhFLcBELCKMIMKWEEsIisCWEVWIiMKMIrAjCjC
+FhAYBgGAYB+IUsIgwDEBgGIFhAYCwgMAwDBRgGAYB+QCxYDIDBRlBkBgRlFfQgMAwIyhYQVgRgGE
+LCKMAwDBRgo+oSj4MLRgRgVgGBGCrEgqMCsCMAwDAMFGAYBgGAYBgGEGFGAYBhBgGVRkQZQZCjKF
+hAYB+RAcAGULeRIEyUGiAyiOOoBgowK/IA/7QIxAf9ADAr+AGpmKjCrYREYCwgWEB9QDAWECwgW6
+CAwDAMA+gBgGVRkQtIgMAwDAMAyg/wCpAfiUHJAYCxYDAMgMpRz1IgyqcQVGgACZAMA/UAwDANAG
+AaANf7gESAYRGFV+gBwAYEYKvkEowIwUYUYBxAFmQD8wlIkKj8wDgIr8OIEYBhRhBwAcBR+oQYBg
+GAcdCgwDATlHQkBx8QDgoTJDAwUcfEA/mUR+IFa5gHIBgRgWwgj8ADAWECwgMAwUfEA/MBMgpZiI
+MKMA+IBgo+IBhKRKBR9QowD9QD9QlH1C1GEGFVyEo/kFqMCsIMCMKMJS3qIFhAYByAYBgGCjkoMg
+WLAsIFhFGAYQsIFhBGAYFYBgRiA+IFYgMCMAxAYgNgowDAMAwD8wDAMAwDAjArAMA/ECMCsFGEH5
+haMFLQIIyg4IDArgIjCjKlLCKPzArIiMKMpSZkFGEGFowD8wDCFhFLCIMK3GYqMBYQLCBYQLCAwD
+AjAMA5AP1AMAwUfAAwUYCZ8ADiQFp8OAgWEByAc9QUsIDn4Ao55dAiNBaOQUYKOQhbgIo/mELdRA
+fmFLCIPxAMFH5lWkzM9Qg/mQGUpbgiQLQWKMIWEUYSj8QtRgWZgIMCWEVX1CJYQVgHIEfgAiQD8w
+KwI4AP0ArgCNgH1BRgGAfqAYC3gIESAsIEz1ATPiAYgMA/AIWEEYVWwDCUYEc8wowLYRB9IC0jIR
+EcIKOfkAYSkT4lUYBvqRBlFZBLFgNhR8P9oBhB+YB+oKPr+ABgqMLRhBgVgS3qIowFhEImADClhA
+sIDCDAOGFLCINcuQCwgWEB8ShYkC3zEEZVW3QkQcgGBGUW3VCCOApYRBgW3oIJYQGIFpEC3QQGAt
+IgMAwDkAwFhAaAW9RAt6iBaRAYBzyAOQFhAYBgRgIniAmfEA2AsIFhAYBgLCINhRyCjAN/EBafEs
+BgGQGEH5hR+ZUR+YVbCIMCMKrCD8wIwKwIwKwDEEYFYEYWjBW4+piowFhAfkEGFGAtAiD8Ao2AYg
+MIWEEsIq2ERLCKtoEQjIRUYQsIpYQLCIW8hAsIowFhELCBYQLCKWEQsIpYQLCBYRCwgWECwgMolu
+IgtkSBYQSzLAsILYkEsWBYRSwiFhAYgWEC0iAwFpEBgowFhAfHjxANAGBGCq/H0AWECJAjYCMvmI
+DAMFIkA5ANgG/iAYByAYKMAwUYBgo56AJkCOCisA/AgjKKyJRoKjKVWERhaMBMgGAYKrCIwK+AEs
+IDC0cBB+IB+AWlhEJkAwUiQUfqAfgCjYEYFfQA1wAMQLCCPqCjAMCsCOQUfp4AHxArAjkA/EAwKw
+IygyAwUYBoAwDBRhBhRlB+gKMFH1AWECwgPgQGUGwgwowlRwBWCowUYFfECPqFGEGAYBgLCAwFhA
+YBgLCAwDAMFLCAwDEBgRgWwgMCWEBgGAsIDKDAMgWECxYFhAsIFhAsIFhAsIFhAsIFhBrZiowUsI
+DAMAwDAMAwFhAYKP1AMAwDC0YQYBgH4AGCj4gGCowKwJMgowDBVfEFGERhaMFGBWEH/EhUYCwgMA
+2ULeZIhYRR+YBlCyEBkBv4FBhEYUfiBXAQYilmIDARJERlWj6gJmOQRZlgRgGCj8+IUsIgwpaBEG
+AYC0QIDgAwFoEB+YBgHAEcAV+ABgIyEBgGAbAWEEZRWQSwgWLAZAsWBbwEC3QQGAt6iBORIDKFhA
+YCwglhBbCBYQLCAxBLCC2EEsILYQS3gIFhAt5iBYQLCBYQLCBYQGIFhAsIJYsFt1JBLFgtiQSxYF
+hAsIFhAsIFhAsIFhAsIFhAsIFhAsIhYRSwgWEQsIFhAYVLCIWECwijECwiDEUsIhYQLCBYRRgLCI
+WECwgWLAsSBYQLCBYQSxYowhYQWwgWEEt5iKWEQsIFhAsIFhAYBgGIDAMQGAsIDAMAwDEEYFYBgG
+CowDBSwgMBaBBu2MIo+JQYCwgPwAMQGCjAMAwDAlhBbCBYQLCCWEC3oIFhAsIFhAsIFhAsIDEC3m
+IFhAsIFhAfmAbEEsWC2JAsIFhBLFgWEFsSCWLBWQRlCwgWECwgWECwgMBYQGELCKPgELCKMAxELC
+KWEKWEQt8hBGFWwiVLCAwFpEUsIDAMA5CFhAYBgH6BSxYhYkCxYoyIWksEsIDClhEGAsIDkA2IFh
+AsIFhAYB/wAQIDAWEBgGClhAYBgGIDAlhBWCjAjAMCsFSwgWEByAYKMAwDAMA/QFGAYSjCjCUYBl
+BkWkyUowUYQYWjCJYRVYSowUb6hRgowlGAsIDBR+YBgGCjjxBRyAsIowgwUYWlhEGAfmCjBRwCow
+UYFYKjAMAyhYkCwgMFJkoWEBkKMAylLCAwUYKMBYRBgH5AGCjBRhaMFLCCOQVbCIlhAsIDCjCUYW
+jCDBR+YWj8whEgGAYCJBRgbljGMhgGAYBgGBGAsIKwJMgGCjAMAwDBVYEYKMIPqFowgwowhYQGVa
+MA5AMJRhUYFYQYKMAwDBRsCMBYQGAYKMFGCjAMAwUsIDAMAwtH4hBgowDgCWEBgWwglhAsIDAMQG
+CjECwgMoMQGSBYRBharAjKDIUsWFGEpYRRhCwgMLRyEowDBSwgjEFYEYWqwgwDYKjArBUYKMFGgU
+YBgowDBS3mIDAPoAYEYFsIUt0EBgRgqsFRwUo/MQo4AMFH4iAwUcApYQGIDAMFGAYiUtAijAWQhR
+gLCIWgRSwgRkIFoERLQIFhAt4iBYQLQIpboIhYQLCBbgIFuoilhELCBYQLCBYQLFgWJAsIJYsCwg
+WECwgWECwgWECwgWECwgWECwgWECwglhBbCBYQLCBYQLCBYQLCCWECwgWECwgOQUsIFhAsIUYCwh
+SwgWECwgWECxYFiQGULCIWJFRlKtpEG4zBRgGClvMQLCAwDgFLCA/MAwUYKj4lKWEKWJAYhSxYDB
+S39BAYKMAwESAtAgWEBgoxClvURC0CBaBFLCAwFoESlhAsIqWERbCA/EBYQSwilhBWESwgWECwil
+hELCBYQpYQLCBbzEUtAiFhBLeJYE5eYgthBLMQLCBbwECwgWECwgWEC3yECwgWECwgWECwglhBbC
+BYQLCBYQLCCWkQLCBYQLSIFhAt4iBYQLCAwDECxYFiQLCBYsBgLEgjKLbxEEsIlLCKWEByBbCCMA
+wFhEowDAWEUsIFhAYBgowFhEGAsIDAMQpYQowtH/AEERGCjAMQGAsIDAWEBlKMgMoWEBgGAYBgGA
+YCwgWECwgWECwglhBbCAwiWEWjAWEBgpYQpYQLCAwDAWEQsIowFhELCBYRSwiFhBLCC2EEsIpYRC
+wgthBLCBYsCwgWJAsWBYQpYQLCKWECwiFhAsIFhAYCwgW8xBGIFhBbCCWECwgWECwgWEG6zBS3oW
+KWJAfEoWJAZRGAYQYUYRbCCMAwDAMKMIPwAMAwtGEGAYCwgMBYQGgDAMAwDAjCqxEGBGAfmCj6AG
+AYBgLIQLCA2WAwFkSAygwDAjEFbAMBYQHAEsIDAMCsIjClhAYRbQIqP0EC3mIFhEGFLCAwhYRRoI
+MRUsIKwiMC2EEsIFhFGAsIKwiWLFGRCxYDAMBYQLCBYQGAYCwgj9ALYQSwgW6iKMIthAYEfqIDhC
+BYQGAsxAshAsID4gLCA4AW4CAxAYCwgliwGBbEgjKD9RAfqAYBgqv1AlhAYCwgMBYQLCBYRCwilh
+AcgGBH5gVyAYCwiIwowDAWEFsIhYQRgLCBYRSwiFhAYKWECwgWEBssBgGQGUSwgMAwFhBWBGAsID
+AMAwUYWjCDAMQH5gLCBYQGAsIFhAcgRgWwgjBSwgWECwhRgLCBbzLAZCjEBlCwgMI3bGEZFhBLeY
+gthBLCC2EEsIFhBbeAglhAsIDEKMQLCBYQGgFhAYCwgWECwgWEBiBYsCwglhAYCwgWEBgLCAwDAW
+kQLCBYRCwijAW6CBYQLCIWEKWEEsIo/MBYQWwiJZCBYQLCBYQHxAWEB+AKMBYQLCBYQLeAilhELC
+AylGFGEqMFLCBYQWwgjAWEUf9REHACwgWgQLCAwFhCjAWEC0CBYQSwgWEFsIIxBbCCMQLCAwFhAs
+IFhAsIFhAsWAwDIUsWBYkCxYFiQLFgWEEsILYREsxFH6BFnIQSwgWEWlhEGAsIpYRC3QQGIFhFLS
+IhYQpYQLdeggMCWEWqwg2ClhAsIDBUsIDClhELCAygxClhAYBiBYQLCBYQLCBYQSwgthBLCC2EKl
+hBWIFhAsIJYQWwglhAsIFhAsIFhAYgMBb8BAsIhYQLCBYQLCFH48AJb5FgWEFsSCWLFLCBYRCchA
+twECwilhELMQLCLSwiFhAsIpYRCwgMBYQLeIgWkQqWEG7YwjIsIFhAsWBYQGAsSIWLFLCBYRCwgW
+EUsIFhBLCAxAsILYREsIFhAYijAWEBhBgLCAwDBRgpYQLCBYQLCCWECwgrBUsILYQS3mIFhAsIDK
+FiQLFgWEBiBYQLeAgRl5iBYQowFvEQLMQS0CC26CCWEC0CBYQLCBbzEC3QQLCA2AtAgWECMhAsIh
+YRSwgWECwgMIjKqsiE5CBYRUYSlpLAsIq2JESxYtLeAiFvmSAylLCBYQLIQSwirb5CIWXGRAfmAt
+4CA2CpYQW0iCTM/EFLR4iBYQLCC26MQqW6CBOQgTkIFvEQLSIFuJYFiQH4cgD8ylLEhR+fAJSxYo
+wlLIRS3UQRwClhEqsLUt/QRKWEUcgqv+gSpYQo/UFLCFLeMiFLCA/QFGClvDmIUsIDBSwgW6sQqN
+yFLCJSZBSwhRlhSxIUfEFHH9oKW4iC2EEt5iBYQHBYUsIFhAsyQLFgW4+ZIFiwSwgMC2EEshAsIE
+5CIWEUt5iBYQLCBZCIWEWlhClhAYQsIFpEVLCItlyEUsIhZiAxBLCFW0iCWECwgWLCliQGUpYQLS
+SBYsKWJAsWBaRBu2MIzLepIhaSwLfIQoxCluAgW/oII+DAMFWJ4gRgpbyEFsID/qCowUfhzBRgow
+UfiCjCDC0YKWEBlKPr0IlGFowlJmCqPwIgwUt6CBaBAtAgjKtLcBELf0ECwgPj/tEWk5RMCBMxIQ
+tx4chFLCITPmCpbgvxEUBVjLixAtxERGgqviEqNQFHMQgi24CCMFLCFLeogTIMLb0EEiYbARlECA
+2IUZSlhAsSAyhYQJy/oSIWEEZVpYQq28eQiJaBFWwiIwpYRCwgWECwilv4kRC3gIFhAsIEZIQH4c
+AUt5cBAsIEZIQIy8hFRgVwIhYQLCBYQSwgWECwgWYhSzkQLeQgWLAsIFvQkBlCwgMFLEgWLBGCrY
+QSwgrAWEEYKMBYQJyEBgGAYCwgRkIFhAYBgLCBYQowIwLYREsItGUGCjAMAwUsIgwDC0sIhYQLCA
+wFhAsIDAWECwgWEEsIFhAsIFhClpECwgMAwFhAsIFhAsIFhAYCwgMBYQLCBYQLCCWLBbEgliwLCA
+wFhAYCwgWECwgWgQLCAxAsIDCUYBgpYRUsIDArCN2xhGaWERbCFSwhSwhSwgWkQLFgMFLCBYQpYQ
+LCFLCBYQLCCW8xAYKthClvMQRgpOXmIFhAsIUsIFvMQLeYgWEKWEBgpYQpYQpYQLCBYQLCBboIFh
+AsIiWEVbCIWEVLFiFhFLCIWEKWgRS0CIWECwilhELCBYQLCFGIDEKWEEsIFhAsIVbCFSwgthBLCB
+YQpYQLCBYQLCBYQLCBYQLCFLCBYQLCBYsCxIJZlgMFLCBYQLCBYQLCFLCBYQLCBYQLCBYQLCBYQL
+CBYREjIRVsIiWEWluoiFhAsIpYQLCBYQLCJSxYpYkQsIpYsCyJEpYsCxIUsIJYsFYCwhSxIJaSwp
+YQWwglhAsIVbCCWECwgWEKW8xAtwEC3qIFhAsIFhClhAsIFhAYKlhClhAsIFhCjBRiFLIQLSIFiw
+GCliRKWLFLCFLCFGCjBUYFYhUsIFhAsIi2EEYgthBLCBYRRhKMFLCBYQLCFGClhAYKWECwgW8xCl
+hAsIUsIJYQq2EKloECxYFhAsSBYQLFhSwgWECwgWEK3bGEZFhELCFLCKlhAt0EQsIFhFLCBYRCwi
+lhAsIhOXmIFkIDAWLFLCBbgSIWLAsIpYREsILYQRgWwglhAsIFxAsIFhAsIFhAsIFhAsIUYCwgWE
+CwglhAtIgWEBiFLCBaRClhAsIUYhSwgWEKWECxYUsIDQCJEEsILYkCxYgwqWERbCKlhAsIFvMQpb
+xEBsFGEJnrIgWECwgW8xAsIUsIpYRCwilvMRKWEC3jIgWYgWEEsIUYCwgr8+AEsIFiwLCBYkCxYF
+hAsIUsIFvQQLCBboIFhAsIFhAsIUsIUt1EKWECwgWEKWhiCW8BAYKWEQsIDC0YCwhSwiFhFLCBYR
+CwilhELCCW+RYLYkEsWC2JFSxYhYRSwiFkIFhClvUQLCBYQJyEC3UQLiBYQLCBYQLT8hAsIFhBLC
+BYQWzEKlkIFhClhAsIlLeAi0sIUsIFhAsIUYhSwhS3UQLCFLFgWEKWn4iBYREsIpb0ECwgrCJaRF
+paRClhEpYQpaRClhAsIFhFGEo/MFLCFGAsIDBSwgMQpbzEKlhCq/MFLCFLCCMFLeYg3beBhGRYQp
+YsCwgOAUbAMA/UA/UAwFhAYB/wBQD6gqP1AWEKr9AVLCC2EKlhAYCwgWECwgMQLCIWEUtAiFhFpY
+QLCJSwgWEUsIhYQSxYpYRFsSAwVLeBYqsiVLFgMKWESj8QFhAsIFokQLMQLeQhS0CA/UBYRS0dRE
+SwgXn+0QH48AVWBLCBaeSECz+IgWEC0iBYQLCBYsBgLSSBbwLAt/QkBlgWmRBLfxIgrBUYBgq2/i
+SQHJSlhES0iA0IDBRgHx4haWESjBRgpGUyIFkID4go/UFHw4cwtGEpYQHAKj8wVbCCWEFtx8hBH6
+gowUsIUfHjJQb5yQpYsEfmCqwDBUcgGClhCjBVtw8xAfgClhBLdeogPiAYSlhFLegiD9QUYWlvQR
+C3oIFoECwgWgQGAtAgWECZgQRgowDgKthESwgWECwgtvUQSxYFiQLFgWJAt/UsCwgWECwgWEC0CF
+SwgthBLCC2EEYCchAsIFhAsIFhAsIFhClhELCBYQLCKMIjAthBLSIpYsQZAsWC2JBLCBYsC3nyEC
+wgWEWlvEQLQIhYQLCCWEFsIDEEsIFhAsIVvWMIyLCBYQpbzEKlpEC3jIgW8+AhVsIVH0BRrqWFWx
+IIylHxBSxIUsIFhCjKUYKW4CFLQIUsIUfmCpYQq2ESpYRSwhRgWzEKWgRKjgLSwhSwhS0CJSMhFL
+R1EQsIpaBEH4gpYQLeYgWEKW+QgWEKTkIFxFLMREsIFhCliwpORIFiwLEgWLClhAsIFhAsIFhBLC
+FWwgWEKW8RClhAsIJGQgWEKWEKWECwgWECwgW8xAsIFhAsIUsIFhAsIFhELeIipYQLFiFhBbEgli
+wJyEKWECxIFvAsCwgWECwgWEUsIiWEVbeYiFhBLCFLCC2gQqW8xAsIFhAsIFhAsIFhAsIFhAsIFv
+EQLCBYQLCCWLAsILYQSwgWECwgWEC3oIFhAsIFhAsIFhAsIFhAsIFhAsIJYQLCIthFSwiFhFpYQL
+CBbzEQsItLCBYRKW8xFLFiFiQGULEgWLBLCCsCWECwgthBLCFLCBYQpYQowtLCIWECwgWEKWEKMA
+wDBSwgWECwglhAsIUsIFhAsIUsIFhAsIFiwGClhAsIFvMQLCBYRC39oilhAsIVu2MIpYQLCCWEFs
+IJYQLCBYQLCBYQLCBYQLCBYQLCFLCBYQLFgWECxIFiwSwgWQgWECwgWECwgWECwgWECwhSwgWECw
+glhBbCCWECwgthAsIJYQWwiJYRS3iIFhELCBYQLCKWEQsIpbwECwiFiwGClpEEsIFhClhAsILYQS
+chAsIDAWEKWEKWEC3gIUsIFhAsIFhAYKWEEsIVbCFLCFSwhSwgWEFYKliwWxIVH8wFiwpYQLCBYQ
+LeYgWECwiFhFLCBYQSwgthBLCBYQLCFLCIs5CCTkIpYQLCBYRKWEUsIFhELCBYQLCFLCBYQSwhVs
+IJYQLCBYsCxIFhAsIFiwLCBYQLeggWECwgWEKWEC3qIFhClhAsIFmIJaRClhAsIFhAt1EC3iIFhA
+sIFhELCKWECwiFhFGClhEpYRRgowhYQLCBYQSxYtLCBYRFmQUt4iCMAwDBRgGCjBRgowUYKPqClh
+ClhAsIJYQq2EBgowVLCAwFhAsIUsIFhClhAtAhSwhS3iIFhAt5iI3bepjGdH5gGEGFGClvQREt1E
+VbCIlhFpYRFYEsIEZQIpaPEQpaBAsIFhAtwEQsIpYRC3AQLCBYQpaBAYCwgWgQSwgthBLCBYQLQW
+FLCBbyEC39RAsIFhAsxAYhSxIFkWBMgqWEFt6iBbxEEsIKxBGClhAt5iAwUbArCVH5/MLVsIVLSI
+UYKWECJ+YBgLCAwlLCKMJUZVqvzBUfjyAtiREZSjC0YKTkImBgowDBRhaW+YiDBRgpb0EBoFGCjB
+SwgMFGBGCjARIKMFVgSwgrBUsIKwJYQowUYB+BSjIUZSjAWJELCAwUcFUYSo/QLVsIiP+oCwiqxE
+SwgMFIyEBgGAnL1EKRPAFIyECwgWEBgLCBb5iFLdBAsIFhClvQQpYRCwijAlvQQInqClhClhAsIF
+hELFilhAYhSwiFiQLFilhAsIJYQWwiJYQLMRSwgWEKWECwgWEQsIpYQHAKWEQsIDEBgLCBYQSwi1
+bCIWECwglhAsIUsIFhAsIFiwLCBYQLCBYQLCBYQSwgtvD5iFLeQiFhFqWECwhSwgWEG9YwjKlhCl
+hCpYQLCFLCFW3mIlSwilhAsIFhClvUsCxIFvAsSliRS3h6lhS0CFLegiVJyEVbCJSxIVLFgWEKrB
+UsIFhAsItLCJSwgWEKWECwgWEKWEC3oIFhClhAsIFhClhBLCC2EEsILYQqWECwhSxYFvMkCwgW8y
+wLEgWEKWLAt4iBYQpYQpYQLCIWEWpYQW3DmIUsIiWEVbCIlhClhFLCBYQpYQpaBELQIUsIFhAsIp
+YRCwglhAsIUsIUt6lhRgpYQLEhSxYUsSBYsCwgWEKW8xClhAsIUsIFhClhBLCC2EKlhClhAsIUsI
+FhAuIFhAsIhYRSwgW8xAsIFhAsIJYQWwhUsWBYRCwi0sIFhELeAilhELCBYQLCBYQLf1ECwhSwhS
+wglhAsIFhAsIFhAsIFhAsIFhClhAsIFhAsIFhAsIFhAsIFiwLEgWLBLCBYQLCBYQLCBYQLCBYRCw
+ilhEpYRSwgWEQsIpYQLSIiWEUsIi2EEuIpYRCwilpESlhAsIUsIUsIDBSwhSxYFhCliQSxYLYQGC
+pYQWwhUsIFhClhAsIN6xhGRYQLCCMBYQLCFLCBYQWwglhAsIFhAsIUsIFhAsIJYsFsSIlixatiQS
+xYhYRSwgWEQsIFhAsIFhAsIUsIUsIFhClhAsIFhBLQIVbCFLCCWECwgWEKWECwgWECwhSwgWECwg
+WECwgWEKWLBLCC2EQsIpYQSwgthEqWEUsIFhAsIFxAsIFhAsIFhELCLSwiFhFpOXiIiW9BAYKWEK
+WECwilhEpYRSwiUcgpYsKWEKWEUsIhYRR8QlLCFLCFLCFLCFGIUsIDBSwglhClkIDAW4iAwUYKWE
+CwgOQUYKMFLCAwDkQLCBYRCwglhFLSIUsIE5CIW4li0sSIWLAYCwgWECwgWEKMFLSIDBSwglhFq2
+EQYgMFLcBBGClhBbCCMFLCBYQLCBYQLCBYQpZCAwVLCC2EKPzEEsIKwVLCBYQq26CCWECxYFhAsI
+FhAuIFhELCKWEC39ogWECwgWECwiFoEVLCBYQWwiJYQLCKWEQsIFhAsIFhAsIFhBLFilhBbEiFhB
+LFhSwgthAsSBYsG5YwjJbCBYQLCBYQSwhRgLCFGAsIDCD9QpYQHxBRgGAsID6sFLdBCjheIKMAwI
+wUtAgOAhYsUsSIW8xAZVLCFLEiDKpYQLCBaRAYSlvERR+oRLCA2BbfxyEC3iIIxAt5CFLCBYQLeQ
+gP1AWgQGCjAMBYQLCA/ABYQLMQLCCW8xBbCCWLBWQRgW0dRCowlLeZYowFhAt58RELCKMFGAsIlL
+CKWECchELCCWEVbQIDgJUsIpYQLCIWECwi0sIhaBAsIFhAsIDAWkQLCBYQLCBGQgWLCpYQWwglhA
+sILYkCwgliwLeIgWEC3iIFhAsIFhAYKW4CBbgIVLCC2EEYgtvMQS0iBbxEBgo+oKWECwgMAwFhAs
+WA+IKlhCqyJkZQsIpMiIP0CjAj6ALCFHASjBR+QWlhEo/AAwowhYQLCBYQLCBYQLCCWEFsIJYQVg
+LCFSwgMFLCAwFhClhAsIUsIFhAZQYEYKWECwgrAjAMBYRCwirbxEEsIgwpYQWwiJYRRgGAaBSwgW
+ECwiFhAsIN2xhGRYQLCBYQLCBYQpYQLeIgWkQLFglhAsIVbEgliwWwglhClhAsIFhAsIFhAsIFvM
+QLCBYQLCBYQLSIFhBLCBYQJyECyECwiFhFLCFLCIWEUsIhafERRlhSxIhYsWlhELMRRgqWEKthEL
+CKWEQsIFvEQSwgWEC3mIpYRCwilhELCBaBFLCIMQLCBOQgWECwgloEFsIVLFgW8SQLCBYQLCBYsC
+xIUsWBYQpYQLCBbzECwiFhFLCFSwgthAsIhYQSwilvURKWEUnIQpYRKWEUsIhYQLCFLCLSwiFhAs
+IFhAsIFhAsIFhAsIJYQLCBYQpORYFhAsSFLFgWECwgWECwhS3mIFhAt5iBYQSwhSwhS3mIFvPiIF
+vMQLCBYQLCFLCBYQLCFLCBZCBYQLCBYQpYQpYRCwhSwhSwhUsWLSwhSwiUsIUsIFhFLCJSwhSwgW
+EWlhELCBYQpYQpYQLCFLCFLCFSwgWECwgrBUsIUsIFhAsIFhAsIFhAsIUsIUt5iJSwi1GUq2ECwi
+UsIUsIJOQi0sIUsIUsIhYRaWECwiUt5iFbtjCMywgWEQsIFhAsIFhAsIUsIFhAsIJYsCxIFiwLCF
+LEgWLClhAsIUsIFhClhAsIUsIFhAYKlhClhAsIFhCrYQpYQqWECwgWECwgW6CBYQpYRKWEUsIUsI
+FuIgMFLCIlixarJEqWLFLQIlLCKWYiUsIpYQLCJSwgWEKWEUsIlLCFLCFLCBYQpYQLCBYQLSIVLC
+C2EKlhClvEQLCBYQoxAsIUshAsIUsIFhAtIhSwgWECwhRlKWJELCLSxYFiRCxYVLCAwtLCJSwhSw
+hSwhRhaMRKWEKMFLCFLCFLCFGClhCluIhUfEFWwhRgpYQqMFGCq5BUsIUsIUsIFiwpYQGClhClhC
+lhAsIUZIUsWBYRCwgWEVLCBYQLCBYRCwi0sIFhELCBYQLCBYQLCBaRAsIFhBLCC2EBgqWEFsIJYQ
+LCBYQWwglhAsWBYQLEgWLAsIFhAsSFLFgWEEsILYQLCCWEKWECwgWEKWECwgWECwgWECwgWEQsIp
+YRCwilhAsIhYQSwi0sIlWwglixSwiUsIFiQLFg3bGEZ0YQsIUsIUsxAYhR9QUYCwhSwgMAxCjAMF
+GCowUYKthAYKMCOPkCjBS3gIUYgWECZ9BCjBRlKWJAtBYhYRaWEKWEQt6iKMIWjxECzEC0dRFIyE
+EnIQGIUsIFhELeYgWECyEUsIUtAiFoEKWEKWEBsQLQIFvEQpYQSZBVsIVLCBYsCxID6ALFgWJAZS
+liQLFgP5kCxYFhAsIVGBZy8BClhClhBH5gpYQWwiFhFSwgW6iIPzBSJ8QFhFpOQiFhFLCIW8xFHx
+5gpaRES0cyxSyJEJyEKthBJkpVtxJAfmCpb1LBbEglvUQpb+pYDEB+oKMBaBCloEC39RAmQUsIDB
+R+gKPqClhBLeohRiAxAsIFhAYFfDh6gqP1BRgpbiID8PQBYQowUYKMBYQH5lKPxIiP0KtWwgMkQY
+UZULeYgjC0t4CIMBbzECwgMAwtGAYSlvCRAYgMFGCjBRwCjBRgRgq2EEYKMC2EKjBRgowUsIFhAY
+BgLCIMKMAwDBRlCxIUZQsIVLCCsBYQLeYgWEEYSlhFLCC2ERLCKWEQsIFhAtAg3bGEZlhClhELCB
+YQLCBYRUsWBYkSlixSwiFhFLCJS3QQLCFLCBYQpYQLCBYQpYQLCBYQLCCWEFsIUYhRgqOeYBgq2E
+EYKWEBgq2ERLCKWEKWECwgWECwhSwgWECwgW8xELCKlvMsKthERgoxFLCItkIJYQpYRaWESlhCjB
+R+IKWkQpbwEKMKMJSwgPzBSwgWECwgMFGAsIIwUsIUsIFhCrYQRgpbxEC3zECwhRgpYsCxIE5CFL
+CBYsCwgWEC3QQGAsIFhBLCFLeogWECwiFhFLCFLCAwUsIhYRSwiFhFLCIWECwgWEEsIUsIFhAsIF
+hAsIFhAsIFhAsIFiwLCBYkCxYUsIFhAsIFhAsIFhBLCBYQLiBYQLCBYQLCBYQLCBYQLCBYQpYQLC
+BYQLCBYQSwgWECwgWLAsIFhELCKWECwiFhAsIFhFLCIWECwgWEEsItWwiUsIJYQLCBYQLCBYRSwi
+FhAsIUtIhSwhSwgMQpYQLCBYQSxYFhAsIFhClhAsIUsIFhClhAsIN2xhFLCBZCBYQLCKWESlhAsI
+FhFpYRCwgWECwgWECwgWEEsILYQSwgWLAsSBYQLCBYQLFgWECwgWECwgWECwgWECwglhBbCCWECw
+gWECwhSwgWECwgWECwgWECwgWECwiFhFLCCWEC0iC2EEjMRCxYFhFLCFLEgWLAsIFhAsIhYQLCBa
+BFLCITkItLCIloEWlhELCBbqIFhClhAsIUsIUsIUsIUsIFhCliwJyJBLFgWEC3oIUsIFhAsIFhAs
+IFhAsIFhAsIFhAsIFhAsIFhAsIJYQLCBYQpYQLCBYQLCFLCIWEUsIFhELCKWECwiFhAsIFhAsIFh
+CpYsUt4iC2ERGItWwiUsIJYQLCBYQpYQpYQpafkIFhAsIUsIFhAsIFhAsIFhBLCC2EEsIFhAsIFh
+AsWBYkCxYUsSBYsKWEBkSlixRiFLCJRhaWgRKMQpYQowUtIglhFqsJSwgjBR/gClhBWCowDBRgow
+UsIUsIUYhRgoxAbBRhKMLSwhSwgj4gowVbCFLCFRgLCDdsYxkWEKMBYQpYQLCBbzECwgWECwgWEC
+wgPzAW8xAsIJYQLeggWgQWwiVLMRSwiE5eYilhAsIFhELFilmSIWLFLCCWEKRkIlLCKrIiMq0sIl
+WwhUsIUsIFhClhAsItLCIW8BClhAsIFhClhClhBGBbeYhUsIVWCj8wJYQpYQo/GQDBR+AhRgo/MF
+HPiEo/Qq0YKPzIlLLrxLFo/AJRkKWEBlKMFLCFRhar9AhaRCj9AVGhCj9QUfyBVsIJEgo4BRgo/D
+kCjBR/1BRwAfmCjBRwCj8wUfQFGCowVX5gowUYKjBRgqvzCVH4haMpRkSjAMFH/UFGUoyA/PgUoy
+FHxKUfiQLehYUYKPzBR+ABgowtH5/IJRgo31BRgowD9AJbgIKwUYKW8BCoxAYBhBhRgowDCUfmFo
+whYQpb+ogMFGCjBUZVqsiDBUfgUqt/EhRgGCk5CCMpRgowUYKMFGClhAsIFvMQLeYhRgGEGFowFv
+MQLCAwFvMQLCCWEFsIDjxCJYRS0CBYQVhEtAi0sIg4EUsIFvEQpYQLCFLCIWEEsWKthELCBYRW4+
+hgyo/AFGCowVbeIhSwhSwhUfUFWwgPzBUsIVXwBUYKMFLCJRlWliRBgoyrRgowlGQoylGFGEpYQp
+EgowUYKMFLCAwDBRgqMFVgRgqsFGBGAYBgGIDAMAwUYKMFGCjBRhKMA0CjBRgGItLCIMFGAsIVIk
+oWEFsSCWLCkyBbEgliwGClhAsIFhAYKWEKWECwgW8RAsIFhClhAsIFhAsIUsIFoEEsIFhClhClhA
+sIUsIFhAsIFhAsIFhAsIhYQLFipYQWwgWERLCKthEqWECwgWECwilhELCBYQLCBYQLCBYQLCBYQL
+CCW9RAsILYQSwgWECwgWECwgWECwgWEKWECxYFhCliQLCBYsCwgWECwgWEEsILYQSwgWECwgWECw
+gWECwgWEKWECwgWECwgWEEsIFhCrYQLCIlhFWwiJYQLCBYRSwiFhFLFiFiQLFgWECwgWEKWECwgW
+ECwgWECwglhAsIFhAsIFhAsIFhAt5iBYQLCBYQowN2xhGRYQLCIWECxYpYkRLFgtiRUsWIthBLCB
+YRSwiFhAsIFhClhAsIE5CBYQLCBYQLCBYQSwgthBLCBYQLCC2EEjIQLCBYQLFgWJAsIFiwLCBYQL
+CBYQLCCWEFsIJYQGAsIFhAsIFhClhAsIFhAsIUYKWECwgMFLCFLCBb0EEYFsxBLCFWwhSwgMJUsI
+owhYQLCBYsCwgWECwilhEpYQpYQLCBYQHMALCBYQSwilhELCBYQLCBYQJyECwgWEKWECwgWECwgW
+ECwgWgQSxYFiQLFgtiQSxYFhAsSBYsCwgWECwgWEC3AQLCBYQpYQLCBYQSwgWEFsIDCJYRSwgWEK
+WEB+oiUsIFhAsIpYRKWECwhSwilhClhELCBYQLCBYsCwglhAsIFvMQLCBYQLCBYQLQIFhAsIFhAs
+IJYQWwhUsILYQLCBYQSwhSwhVsIVLCBYQLCBYQLCFLCFLFgWJAsIhYRSxYFhAsSAylGCjCUsIDBS
+wilhEqMFHICZEKrBUYWlhEowtb1vMwipboIFhAsIowhbzECwgW4iBYQLeYgWEEsWFW0Egj9CwLEh
+SxYFhCq4IJYsC0CBbwECwgW8xAsIFvEQLCBbwEBgLfIQLCCWkQGELCKWECwgWECwiFpEUsIhYQGA
+sIDkFLCBYRRoRKWEUchB+JQckKWLAmQVLCCuQDAlhAsuogMKW8xEGClvMQLCBYRS3mIiW6CC2fUQ
+H5gLCAwDAWECwglhClhAsIFhAYKMAwFhAsWBYkBgLCAwFiwLCIOCLRlCwgWkRBiKWEQsIqMQGCjB
+RyEGAYKMFHIKMAwUsIUsIUfmAfmClvMQHIKWXUQR+YFfACMFV+YKj8wDgBbzEB+ZSj8yJRhRlB+Y
+KPzAMFGAs+pIlHBVpYRKMFLCA/UQowUYBhaMJRwCjBRwAYKjAMFGCjEBgowUYBgowUcgo5BSJBRy
+CjBRhKMFHIWjBRhKMLRgo5CUcgo5C0YSjBUYKMFGUoyFH5gpbzEBlKPzIUYB+YKMoPzBRgo48eYK
+MFH5go/QFLQIDgFGAcCFS0eIhW6zFnSwhSwiUYKWEEsIowlWwglpEB+ohRgpYQo5ECwhRgpYQJyE
+BgGCjEKWEKOQFhClvMQS3mIVbCBbzEEYC3EQLcRAsWBYkKMFLCBYsKWEKWEKMFGClhAsSFGUowUs
+IUsIIwDArAlhCjBVmQlRwCjBSwgMAwUYKMBYQGCjBRgGCjBRgo5BRyCjBRgo5AOQUYSjC0YSjCjB
+RgowlGCowUYKMFGUo/MFLEhRlKMFGCjAPzAMFGCjBRgowVHAKrgFHEgGAbBRgqWgQLQIDgBaBAcA
+paBCjAOAUtAgWgRC0CKWEKWEC0CBaBClhAtAiFhBLFirORIhYQpYQLCCWLBbCKlhClhEpaRAsIpY
+RCwgWECwgWECwgWECwgWECwgWECwgWECwgWEEYhSwgWECwgWECwhS3QQLCBYQLCAxAsIDRQsSBYs
+CwgMQRgqsCWECwgMCsFLCCMRKMKWECwgWEQsIpYRBhRhKMKMJRgGFpYRCwijCFhCjAMFbjMWVH5g
+owUYKMAwUYKMFHAKjBVYBwCpaBBXAKloEC3QQLQIFoYgWEBwEpYRSwgWgQLcRELCKWECwgliwWxI
+hYQLQIqWLELCKtiREt4FilhELCBaBClhAsIFhAsIFhAsIFhAsIFhAsIJYQWwgWEKlhBbCFSwgRkI
+Fp8RAsIFhAsIFhAsIDBSwgMBYQLeZYUt/QkCwiFhFJyLBGBbeIhUYhRiAwFhAYCwhSwgWEQsIFhF
+LCIMKMJSwijBRhBgowUsIDBRgpYQowIwFhFWwiJYQGCjBS3QQGAYBgGIDBRlBkBlCwgMAwDECwgM
+BYQGCpYQGCqwgwtLCIjC0YKMAwDAMAwgwDC0YQYKMFGAYBgGAYBgowUYEYKMFGCjBSwgWECxYDBR
+kCxYDIUYgWLAsIFhAYCwhSwgWECwgWEBgLCAwDAjAWEQsID8xFLCBYRCwilhEGFLCBYQLCJSwi0s
+IFhCpbiWC2JAtAhS0CBaBEbjMWYwDCFhAYBgGAYEYBiAwDAMFGAYBgGAYBgGUowUYBkBlBgGAYEf
+mAYFYKWXUQGCowDAMAwUYBgLCBbwEBgHIBhBgH4hRgLCIMAwtGEGFowiMAwowlGAsIDAWEBgGVRk
+QcgGULCAwDBR+YBzABgGAsIFhAYBgIkAxBLCCsAwDAlhAYKrAj8wFhAYQYUt0ECwgWEQjIRS3mID
+6zIQsIoxELdBAcASxYq2JELMRUZULCKWEQsIpaORIhE8CqWEC3ERC3qIFhFLCIWECwgWEUsIhYQS
+wgWEFsIJYRSwiFhAsIFhAsIFhAsIFhAtIgWECwgWECwhS0iAwDAWkQLeIgWkQHJRGAYFYEsIDAWk
+QGAYQYUYBgGAYhS3mIFhAfmEH5hRgRgGEGFGAYSjCjCDBRgGFGUGRKMAwDKDCjCDBRkBlBkBlBgH
+4AGAYEYFYEYG7YwjKlhClhCjECwgWEQsIFoECwgWECwhUsIq2ECwiFhAsIFhBLFgTkIFhAsIFhAt
+1ECwgWECwgWECwgWECwilhELMQLCBYQLCBYQSwgWECwgWECwgWECwgWECwgWEC0iBYQLCBYQLFgW
+ECxIFiwLCCWECwgWEBgLCBYQowhYRRgLCFGAYCwiDCjBRgGCjCDAMAwJYRRhKrAlhAYKMAwDBRgG
+CjAMFGUGQGULCAyAwDKFiQGUGBGCrYQRgVgGIDAjAMBYQGAsIgwtLCAwUsIDBRiFGClhEowDBRha
+WESjBRgowD4ARgVgqMFVgqMFGBWCowUYKWEBgowDBRgGCjKUYBgowUZCjKUYKMJRgowtGAYKMIMF
+RgowqsJUYWjCKwIwUYKMFGAYBgowUYKMFGCjAMAwUYKMAwUYBgowUYKMAwUYKMFGCjAMFGErWzFm
+MpRgRgVgRgqsQowDBUYKthBGAYBgGEGFowDBRgpYRBgGFowgwUYgMFGAYBgpYQowUYBgoxAYBgRg
+qsQRgGAYBgGCj8wDBRgowUZSjAMFLEhRlKMAwlGFowgwUYEYVWEGClhFowgwtRhBgqsFRgowUYBg
+owDAMAwUYBgowUYKMFGAYBgowlGFowDCUYUYKMFLCFRgqsFLCJRhajBRgowlGCjAMFGUowUYKMFG
+AYKMFGCjAMAwUYKMAwDAMAwVGBbCAwVLCAwFhBWCowDAMQGCjAMBYQGCjEKWEC3gIFhAsIDAMBYQ
+LCIlixSwiDClhELCKWEKWECwgWEQsIpYRCwgWEUYQsIFhAsIDAWECwgWECwgWECwgWEC3iIVLCCs
+CWEFsIJYQLCBYQGAsIDBSwgMBYQGAYCwgMAyhYQGQbjMVowtGCjAMJUYKMFVgqMFGUowUZCjArAj
+KUYKMFGCjBRgGAYKMAwDAMAwDAMAwIwKwJYQGAYCwhSwgWEBgpYQLeYgMAxAYBgLCA/EIWEWjCFh
+FLCIliwWxIqWLEWxIDBUsWBYRVYEsIg/UBYRVYRGAYBgLCAwDAWEBgGClhAtIgWEBgLCAwI5APwA
+rAlhBWBGAfmIDAMBaRAYByIDCDKo+HMgMAwD8wUZQYCwgMAwIwDkBYQVgqMAwDAMAwDBRhCwgMKM
+IPgFGELCKMIMAwpYRBhUYFsIgwUYBgRgGBWBGIDAMAwDAOQDAMAygwDBRgowDBRgowDBR+gKMAwD
+AjBVYBgRgowDkFGCrYRCJCpYQGCjAMAwgwowUYKMAwUYBhKMKMAwlGIIwqsIjCjCKwDEEYUYFYRH
+IFYGuxjGRYQLCBYQGAsIUsIFhAsIDAMolhBWAsIDAWEEsIDAWEBgGILYQRgGEowDCjAMAwlLCKMF
+LCCMQVgGEGFGELCCMKrERGBWBGAYBhaMIMFGAYBgHJQZAYBgLCAylLEgWLCjEBgGAYBgowFhBLCF
+VgowI2ClhBWBGAsIDAMFGCjAMAwFhAYBgGAsIDAMAwgwDCjCIwtVgGEGAYCwgMFGAYKjArBUYBlU
+YQZAYBlKMAwDAMA+pCjKFhAYBgGCjCDCowK+IEsIDArAMCPxBRgVgqMAwDBRgGAYBgGAfmAYKMA/
+GQDAPzAMJRgowDBRgowUmQtGCjCDAMFGCjBRgGCoylJkCshUZSjBRgHABgowKyCMorIJYsCwgMA/
+MFLCA4AWEQYUcAGCjCD+YVGAYFYBgLQIiWEVX0CNbMWVGFSwiKwUYEYBgqsFRgLepYFhBWQqMoP0
+IUclB+gKMAwUYKMA/wCoKWEBgGCjAMA5AMFJyEBgGEowowlGFGuYKOQlRhRgowlGFo3yAWEBgowD
+8wUYSjCj8AUsIg/AFGAYKNhaMIMFGFo0EH4haNBCZBRoFHADh1BRgqMFHBRWQqMorIVHHQoMQHwA
+rggjKDBRgo4fkAcAGAYBgpYQGAt4CAwUsIFhAsIUsIFhAYEsIK/UBb1EC3iIJYQLCIrCo4AOQFhA
+sIFhAtIgMIWEUsIhYsKWJAYCwgMQLeghRlKP0BRkC0lgWEC3AkBqCiMFVgRgVgHIKWEEYKMFLSID
+BRgGAYByCjBR8QFvMQH1BRgo/MFGAYBgowUYBhBhRhKMLUYKMFVhKjC0cAowKwUYEYQYWjCUf9gW
+jCUYUYKMIMFGCjAMpR+BCjAMFHABlKMAwDAMAwUYKMBYQGCjgBEgSZBVYKMDWzFkjAthBGBbCIjC
+lhAsIEyAsIFhAcAHxAWEBwAtwEC09BAsIlGAYUcFBsiD8wFixUYQsIKyFGURgW3qIFhBGBXIEYFf
+iAYEYByCjAMA5AMAwDAMFH4gGAYBgGEowowUYSjCjCUiQowD8wgwowDCDCjAOegQfyCjCIwoCq/k
+EAVPiCjAoACMFGAcwCjgByBRgGAYBlKMgMA/MIOAoylGAYBgqMCsAwIwK/GQUYEYBgHIBgGAfmAY
+BgGAfzAMFGEGFGEGAYUYQYUYQYCwgWEBgowDAMBYQHIBgRgGAYKMAwDAMAylGQowDKDIDKUfAAwl
+GFowUYKMAwDAMFGABQFGCjCUYBgGFGEowUYKMCMFVgowUYEYFBUYAFGAYAFVgqMFGAYKMAwDAMFG
+CjAMFGCjBRgGCjBRgrWyMhwAYKjgFIkCsAwUYBgHAEYKMFV8AUYEmfAA4AMIMKMIMAwDAMKWEQYU
+sIgwDAMFGCjAMAwDAMFGAYhRgGCjAlhBWCjBUfoBWCowDArEEYBlBkKMpRkCzEBlBgGAYBgowlGg
+owUYBgGAYKMFGEGBGAYVX5hB+AVGEVgqMLRhBgowDBRgowUYBgowUYKMFGCjAMFGCjBRgGCjBRgo
+wUYKMAwg0FGEGFowUYKMAwIyishUYRWBGVVZCoypVZFqMqDBRgGQoygwDAMAwDAMAwDAMAwDBRgG
+AYBgGAYKMAwDBRgGCjAMAwgwqMCsIjC0YBgGAYBgGAYBhBgGFGEGFGEowDCjCUYUYQZQZFGEGFGE
+RlUYCwiKwDAjAr8QIwKwIwDAMAwDgAwDA1sxZDBRgowUYBgGCjBRgGCjBRgGCjBRgowDCUYKMLUY
+KMorIlGBGAZSjBRgGCjBRgowtGCjCUYBgowUYBgGAmQUYBgowDAMAwDAMAwVGAYFYKMFRgVgRgVg
+qMFGAYKWEBgGAYSjAMKMFGAYSjBRhRgowDAMIMKMIMLRhCxYIwDArC0YRGAYFfmCjBUYBgGAYKMQ
+LCAwUYBgGAsIDBRgH5gLCA/MAwDEBgHACwgP1BSwgWgQRgq2ERGFGAYBgLCAwDAMAwDLAsSBYQLF
+gMAwFhAZAZQYEcgWwgWEQYVLCIrAjEUYBhBhRgowgwowhYQGAYBgGCjAMAwDAMAwowgwDBR+oBgG
+CjBRyBGCqwIwVWBGAYKMAwUYBlKMgMAwUYQYWjBRlKMFGQGUowDIVrZFGAYKMAwDAMA+oBgGAYBg
+GAYKMAwDBRgo+gBgowDAMCPwAMAwUYBgJkBYQGAsIFhAYB8AD8QE5f1LAZAZQYCchAsSCWLBX1Ig
+yqlpEFiQIwhYRRgWwgjYBhCwirYREYCwijAMINAH0AMKMIMAwUYKMA5AMCMCsAwIwKwDAjCqwiMF
+GAfgAYKMAwUYKMFGCjAMoMgMoMgMpRgGQGUowDCUYWjAMAwlGCjBRgowowlGFGEowowUYQYKAowI
+Cj4AGAYKMCsFRgGAYKAGCjAMFGCjBQFGCgKAoCgKMFAlAUYKBaMAEoFoEowAUCUBQFAUBQFAUBRg
+GCjBQFAUYAFAUYKAoEoCjC0BUAoSkSFGEowUYWoygQoyikSowKCjQEf9pQcEDgUoQoyg4IDjmVWp
+mLIYBgowDKDIUZQYKMFGCjBRhKMAwowlGFowgwDAMAwVGFqsJRgGCjBUfqCnwBRgVgqMFAVQVGAY
+BgowUBT4AowUYKMFGCjBQJRhQFAlAtGEoCgWnEJRhaBKPwC0CHxBQFAAKAoABQFAUYAFAUBQFJkF
+AUBQFGCjAPxBR+PMFAUYBgoCjCUYKNBaBKjCqwlGCgKMFRgowKwUBUcFBkKMA4AMq0YSjIUcSUo4
+BRgGQpMlBgo4ANgGCjBRwAcAGCjgFHAKP1BRhBhaPgCowVX6AowDCI+IWq/QA4Aj9AUcBBhaMIMA
+4+YBhRhBhRwEowowg+oUYQYBgGCjAMBM+gWjKlGQo+oB+IKPxAMCMorIJElFZBGUqsgjKUYKMFGA
+YBgOAKMAwDAMA/AFGCjAMFGAcdQlH4BaMFHAKP8AsBUBVfgCjCUYWjCAK1sxZVJkFVgowIyg4IDB
+Rgo4KDBQhRlKMAwDAMAwDBRgGAYBgo4APh/sBR9OoKMA4gFGAYEfHgCqwUYQYUYQYUYRHxCjCDgK
+MCvxCIwowlGFGwhYQHw4gHIBgGIFpEBgGIUYKMAwE5CAwUYBgHIKMFGAsID8AESBLFgrIIylWxII
+ylHIKMBb1EBgVkEZQsIDBRgowUYQYUYQYWjAPiAYSj8QtGEowtGEGFowlH5gowDCjCVGFqvpAQYB
+gR+gKrBUYKMFGCjAMFGAYKMFGCjBRgowDBR8fMFGCjBRgowUYKMFGCjCUYWjCDKDC0ZEoyrRkKMJ
+UZVqsJRgowVGCqwowlRgGBWCowDBRgGAYBgowUYhRgGAYKMAwDBRgGCjBRgowUYBgGAYBgGAYgMF
+GBGEGFGAYBgGAYBgGEGFGEGAZQYUZAZRrZitGFGEGAYBgowDAjBRgowVWCjAjAMAwUYBgowUYKMF
+GAYKMFGCjAMAwUYQYWjAMAygyIMFGCjKowDIDKlGQoyqMJRgGQGUGCowDBVYEYKPgAYKMAwDBRgo
+wDAMAwDBRgowUaBRgowDBRsAwUYKMJSJYUYKWEBgH8wDCUfgFH4gRgGEGAfqFGCq5/qERgowUYUf
+EBYsQZCjAMAwDBRgH6Aoyg/AA0AYBgGCjAMFGAYKMAwVGFGEVyBGCjBVYEYBsFGEowo5BRgGAYBg
+GEGFowDAMAwUYKMAwDAMCMJVYKMAwowIwUYQYBgGAYBlKMAwDCjAMIMAwDBRgowUYKMAwDAMAwDA
+MAwDAMFRgowKwDAMCMAwKwIwDAMAwUYSjCjBRgGAYSjC1uMxZVGAfAAwgwoygwDIgwowgygyAwDK
+DBRhRhCwgMCMCsAwDAMA+IBgRgIkAwD4gVgqMAwDAPxAMAwDAMC2EEYBgH5gGEGFowD9Ag5Cj9QD
+APxAMIjCqwiMKMAwKwiMqjIgwDAMqjIgwUYKWLFGRBlBgGAYUYQYBgGAYBgH48wDBRgGAsIDAMCM
+CsAwDAMFRgGAYFYKjAMCsCMFGEo/MAwowgAcgGFowUYBhBhRhKMLRhBgGCjBR+oBgGCjBRgGCjAM
+FGAYAFGCjAMAwDAjKABkFYKjKUIDAMoMAwUYBgoCjBRgowgwDBRhaBKMLRgowUYQYBgowUYUYSjB
+QFGAYBgGCjAMAwDAMFGCjAMAwIwKwDAMFIkFSZBRgqsCMAwDAMJVYGpkZDCjAMCMJRhaMAwUYKMA
+wlGFowgwDCjCDAMLRhKMKMIMFGFowgwowgwUYKMFGAYKMFGAYKAowVGUVkBgowIyishRgGBGUowD
+BRgGAZAKUYKMFGEowUC0YKAowUYQYKMFGCjBRgGFGEowUYAFGABRgowUYBgGCjBRgowDCIwtVgqM
+FVgo2AYBgqMCsIMKjArAjAMFGCjAMAwDAMAwDAMIMLRgGAsIDEQcAowDCjAWEQcFgWEBgGClhAYE
+sIDArAjAWEFYEYBgGAYBgLCAwDAMAwDAP0BRgGAYBgowDCVHIWqwDkAwlHIUchBgGCjCjkIMCMLR
+yEqsFGCowVWCjAjBQFUFRgoCgKcQUBQFGCjAApIKAoCgKAoCgSjC0KUYKMFAUIUCVqZGdGAYKMFV
+gqMIMKP0AMAwDBRgGEGAYBhRhBhRgGUGRBgoyqMiDAMAyhYQRgVgGAYEYBiAwDAMAwD9QDBRgGAY
+BgGAcgGAYBgowDBRgHIBhBhRyEGCjC0cgqMA5ArAkzIKMJRsA5BSZC05hKMFAtVhKjmADBRgowUY
+KAowUBRlKEKAowDBRgowUBRlKEKAoCjKUABKBaAoRKFUBQJRgoCgKAo4BRgoCgKAoCgKMFGAYKAG
+AYEYKMFVgqAowUYFYKAoEowAWoCqCowUYQYFBUYBgAUBTiCqCowUYBgGABRgowUYBgowUYBgGCjA
+OADAMAwUYKMFGCjAMAwgwowUYKMIMAwDAMKj8wDAMIMCsFRgGAZQZBWBGUGQGUGAYBkGojOjBQFA
+UBQAUo+hChSgKAoCgKESgKMLRhKFKBaBBgAUC0CUBQFAtAlGCgKAowVAUBVBQFIBUYKoKBKMLQFQ
+FUFGCoEo4BRhRgqhKjAAowDBRgAUYAKTIRQIwDBRgGAaBRgAUYBgowUYKMFGCjAMFGAYKMAwDgFG
+EHABgGFowiPzCjBVYRGFVoBMhBgqPzAMCsCMoMgMA/UoMKMiDKDAWEB+gKMAwDBUYFYEYFYCwgjA
+rAWEBgGAsIIwDAWEFYBgSwgMAwDAMAwDAMAwDAWEBgGAsIDBRgGAYBgGAYQsIIygyKMJVYKj9SlG
+Qq2EEfmBWCjBUfUpRgowUYKMFGCjBRgowUYKMFGCjBRgowDBRgowUYKMFGCjBRgowUYKMFGEGFqB
+KrC0YBgrUzFVYEYUYBgGVBgGQGUGAYBgH1IDKDAMAwDAWECwgMBYQGAsIIwKwJYQWwgjAMCsAwIw
+DAMAwDQBgGAYKMAwDAMAwDAOQDAMAwlGFowIwKwlGAYKjCjCDKUfzBRgGQowDBRgoylGCjANAowU
+iQUYKMFGCjBRgowUYKMFGCjBRgowDBRgqMFVhKMKMIMFRhaMJVYKMFRhaMJRgowVWCowUYKMFGCj
+BRgowUiQUcgowUBRgowUYKMFGCjBRgowUBRgoCjBQFAUYSjBQFGCjAMAwtCpRkKMAwVGUowUYKrI
+UYKMFGilR/iCqwUZCjKUYKjBRvgCqCowUYKMACjBRgqsCMFAUYKMFGEowUYKMFGFowlGFAgwDBR+
+YKMFGAYKMA2BHAKrAMAwUYKNARgVgowDAOOYK1MjKowDBRgoCjBRsFGCgKMFGCjAAowUYKMFGCjB
+QFHIBgoCjAOQUcAowUYKMFAlGCjC0fqCjABKMAwtGEowUYEZSgKrBQFGQqFKpAZSjBRkKhSqyFRl
+KMFVkEZSgKrBUYAFAgwU8gtVkEKUYFYSowtGCjCDBQLRhBgowU/mCjAfiAYBgGA4Ap8OIKOAUYBg
+owUYAFGAYBgH0AMCOAD8wKwI4QFAj4oAwEyCjAPzAMCthKjCjAdQiv5gGBHxCjAMAwg4ATPEAwDK
+oyIMoPoRRlQYBwQLFgPqQGUGBGAYBgVzABgRgGAYBgGAYB/MAwDBRyAYB/MAwUYBgH5gGBGBWgDE
+BgH6gR+YKrAMFH5go/MFQJRhVYQYUYSjBRgoCowUBQFAUcgowUYApRkBgpxBR+AKOQUKUYBgAUIU
+KUYK1mLIYBgGAYKMAwDAOAD/AI6gGgDAMCOADAPwArAMFRgowKwiMKMAwDAP0CDBRwCj8Qo2EJkA
+wDKtGRBlUYByRBuPMA+BQfEgPzAjKqzIQfiBGAYFcgRgHIByBbCCOQFpECJAOQFpEBgH1APgAYBg
+GClpEByCo2BWAYBgGAYBgo+oBgHPwBRgRgqsIMFHIBgJkLRgLCIRMgGAYEaArAjAMFWZAjAPoCjA
+FKMhQpRkAFGUGQpEgowUYBvmChSj8wUYAhRlKPwCUYWjBR9AlAtHxBQJQFGCgKMFAUBRgoCgKgKf
+zBVBUBVBUYKAoCr8AUYKAqdQVQUBU6gqhKjCnzBRgoCqEqAoCjBRgoCgKAowUfHgCgKAoCkgoCj8
+wU4Ap8QUBQFAUBQFAU/EFAUBTgCgAFGCgKAGCjAMFAg/AFGAYWgQ+YFZGdGCjBRsFAUcFKMhRgoC
+jAMFH6AGUoCgKMFH1IDKUBQFH6EKFShCjKtGAYKMFGCjCUBUYKvkCgKAo+IKfAFGCgKMFAUBQFGC
+gKgKrBUBRgqgqAowlAtVhKgKoWgSoCnHqCqwVAUBQFAUBQFWQVAUYKsgqMFOoKApM/MFJ/EFGCjj
+xBSJAMFIkFOXAFR/MCzwBQFG+IKSCjCHILSJ4go/EJRhRwAcApAKN8ggwtSclzESq/TxBUcAqsFG
+Cj8AIwVWBGCgFfkgVH0Av8wVGCq/D+wCMoPzAP1ArII+pQYB9SAyg+IB+gBgH6AGQGUH0AP5AGAY
+B9QDAMAwDCFhFH0CDAMKMIjANgGFH/YEGBWBH/UCvx+QEYBgHIFaAjAoKjj5gUFRgVgR9ADAMFGA
+YKMA/wAQDBSwgOAD9ADBRhKjRSjCUYWjBViSFGFOAKMAwUYBgH/EBEKtVyRB/wASCkyBGUowK+r4
+kK1OCMj+GA5AqdQKCgKjAvUAwVHxBVcAH0AjAMAyisgMFGwVJlc+QKPxKK/H1II38CivoQqP0KUj
+kCjAMBEgIkBM+IBgpEkB+BQb5ApMgVv+wiVIkqkTPUFADfIA3PAIjCq5An8wLM+oQb5BR/IAwDCD
+9fUA/gBH6gWeQKMCceoKMBMrkAYBgo55AInoAYFc+AEYCZ8AK4YE4yCjkAwD+QBgOL8wD48wUfAB
+058AD+YB9QLYQT+YB8QET8wgwo1wAAGAYKMFR/IoWEFZEGAieABsKMITPoAYKMCP1KUjxBV4RzII
+ylGAfiCjAPgCjAAoAYByCn8wD4gowU5AowUYKMFOQKMFGAYBgowUYKMFAUBRgowlGCjBQFGCjBRg
+oCowVX/UFRgqgowVAUYKMFGCgKrAjgFHAKMFGCjBRgAUYKOAUYKMFGCjBRgoCjANgo0EowtAUYSo
+ypSZCVpy1McIecxjDTnhxkuMZyx23xr45WMn5iLRkWrEharItGUowUBRgowUYKjBVBRgoCowVWQo
+wVqfAjMYKPqESJKtV9CAwg+LAkTxKqv5kRH0KpMsA5CK+vUgOQDYVH/QqK5iOBBOfxKUYKrIIyg1
+5AGAiQUc/EBEyAgFHK5gJkAwUYBgo+kAo/EBEriAAP8AqCjAAGCjBUYKv4AqBKr6AowtGCjAMFGE
+QLVfXqEpE+ABgowJIKAowLEgqPxAr4IFGCowUYKMAwUYKAowUYKMFGCjBRgpzBRgowUYKMFGCjBR
+go4CUYBhaNApEsJRgowUZQmSFGCjBRlBgowUBQFCAylGwUfQhUZSjBVmQDBRgoCgKjArBRgqRIBg
+GAfQAwDBRgowABgGCj48wgCjgA/AA+IWjCDC0ceIQYBgPMAwDBRgHAKMFGAYBgGAYKMAwDAfABE+
+YBgGBGAAvyAjhlByQAAKN8I6FB/gRB+AWjCUf9pVGRKOQDfyAMA/PiChUrSysa7Hs+12U5avc+7a
+M6+x0tPVjbbWf06+po1y1YyUTljhjhPHLHiavcb79NOPM2znFz8MZ8Plb8eja7Xh029XLy4umNdp
+j/NnH83zmMeeOrV7h2W22Pc9Sdjjjj23eRG72Wng5ww0tb88YYzPOMWo8idpybcnHj1fza/lz9eP
+P7WXe9vrw8n5P5Nvza/CZ6zHy+DrIyNuNKj8CMqr8QtVkWjAcADKE9ADXkCj8OMgQABX5oA/EAwD
+48/kBqZiyoylGQowUBUZSjBVYAFGCjBUYKMFVoFQFUFAUBRgqAqtAqNgGAYKMFGBQVGCjCUYKTKB
+RgowDAAowtGEGCgKOAUaBRgIn+oKMFJkFAUYKMFRgV+AACMA/mCq/ECOQVZ8gVJmAKwIwKwJEgqs
+B5AGBH/Qor8SFSQlOYWqwiMLR8UBWESZAAo/MA/kFo+JUGQo/QBIKN+QKMFPxgpR9QHIBMkCZBRw
+AfQA/mCkz8ygyFH6lAhSZKEzHwAMCOFwBV+IB9SCPpHIoAV8QVGBZkCN8egBgo1/uAr9AESAYKgK
+Ao+gBgGAfFgowlP5BavwCI+nUKMIPqAYBgGAYBgHxBRoA5APr1AP+oBgGCjBSeIKMAwDAMCAVgGC
+o/4YKoKMFRgVgGBPMFH4gVgqSCgFYQC1AUkpRkSjArC1AUchKMpRgpMgqMJUmSsc5SZKxzlsaG/3
+O21NljqRjhGjqampwcxlGssc7fL8DPbi12xtPPGPw8HBw93yce3HjbGMenO2fr9XTa/TwZH3DWyy
+7VnsNXCMf8pzxnQywmcsZ2WtM0ltzXJ48uEI6vh1nJ68f/k8f+bHj+HV6XuenDtxZxjH6OcZxOv+
+nt4Z+zNx8sR0LOxdBWqJIyqsMq1NkKMLRgowtHJCgKFKAOfMFGQoCgKrkFUMh9HxAPxBUfgBYkFI
+l9QHn0BUYF8wVH4AP4QFfqBH1Ar4AHwAkz0kA+IFfoBI8PwBSZ/qCkyAYCJ6gHDAP4MAwgwo4CU4
++HAA2AfUA56AJkA/AKMJT+YKMFHxAOQDAP0AMBMz4gowDQBgp/HgAmWBAVWCnMAAYBgqfMFVgGCp
+xAr8QIAfiBWEo0BH5haMFVhBgqMFAUYWgKMAVKMinxCUCgSjBQpVIICgKMCgqeRSkEKFKfAhQpTz
+BQhQFClAUIUKBCnxKUBTmQp/DKUCUBQLTzBQJQFAtAUCU4kKFKAoQoUoCgKfMFAHAFAUBRgqRIKo
+KfAFAU5ApwBRgqAqv1BQFAU8gASgKMKMFOQSgKMACpMgqgo45dQUYEBTqBQUBQFGCnwBRgAVGCqw
+DBUYB+PAFH4AGCkyEqMJRlSj4AqWEStM5FiVGVjnLjbqLYRlHGcMolR4Twk5dPFq8+LjGfhlkXbd
+PZbvW2WO+1Yw0dXT1dr9fUxnKNN4xlpzlGMxlGMTCZ1nNnfTG3px1xnGZ8fi9Z2+OPk34vXnptrt
+pc+XT1a3zmM4l+x0eMzDxlTWZhw1K6wzsMvN9cdGqMjGLWqMiRlVt+Ai1bfMRarItGRar+YWjQKP
+xCj+YQBTn8gUYEYDj5ArUGYyB5lKMhTmUPgCgKACFClCFClCFAUBT4FKfIAEoCgKEApQLT+REoUp
+5goCgKfwgBCnxKUBQFPiCgKEKFKeYKAoCj/iAUBQFAUAAoA5goCj8QUAAoEoFP5go38QgFp/DCVI
+8OgFAAo+gD4AqTK5goCr1AfgCjBUBVfzAMFR+ABgpC5AAK/45gqPwAOOQKP+IAMCgqMCvivxAj/i
+AVWCo+IB8fMA+LBT8ZATPkAYSkTAUfAA+kAH0AfDkEPwgLTzCUfQA+iATKBRgowHLkCnEFPOYBTg
+BP5FBogv8MFGAmQUmf6gIBQA/UFGBHJRX0nqQOHgCo1/uKHPiCqyAwDAMCfIFH0APoUoAZBX1Ajk
+pVfyIiPw5lVWQTlx/EorfAgnl0KE/gCnAAwK38SIhQYD4AH6gHABgGFQJVsIFhAcAGuXIA18wIBW
+AYCwgjj5gGCjCVpmepUqTkWMaWESpOUiCRMlTqjXQsY5uEsIxrTl+bGcZ6wpLjox2xcR2vbNXTx2
+23y19D9zGlrTnr7CdTLRjWwia5ac6mKywiZ5zjJq82mc7ZmfTcT1S+n5zz+p2WnPjHaYzt/Rtttt
+r6vT6sYx11uOuOvidyw/zDumlp9t2OG0w3UV2nbtvbLHHrEY5ZzM5tzxZOG8fHnO+2dvT47Z/f8A
+BefH+45ePHDrjGOTXHo11+/p558Xax9vfdP0vrZbSuM8FlnEZPwq3Pyg08+7dtZ6noNP7R9w2xZj
+/uw42Xsz3LjnOH7DPLKOcYrKY+K4x8zlx7j2+cX1YcG39r+46/8A4/xx/Ft5e1fcOEOdjnx5cY/3
+mWO+4M/1YYZ/tr3HH/4s/h/F1u42m72eU47rRz0smvz4zEOPCTa05Nd/5c103cdpz9vmcum2n14j
+ZjM5I1a1WZIyqxMfAi0YZVWRaOPAAwEfAAwIyisg1RPUjIAP0CDYUfEACkzwAP8AoEOQABzAeQKB
+U+PPxCDAv4ApH4gpM+gD48gD+QKn8fwylV/MhUsWC9SFRsorXkQGCo4KK+JBGUXkQSZnoUX4EAB1
+BU/kUP4YKPqCj9QDBR+IBz4kKP5lFkghSj8ADRA4FKMFGugFaIiOCqOP6AGEGAYCZAMFH4gqP+JB
+VfzgAwD48AEzIEmYkFVxABxACZAMA4AjgFVwAmfmBGBWp5gR+YKMCzk/gII+ABgOAKPzARK+AKMA
+wUYAAwDQBgGAYBgRlFZAbAOQBQfiQGAYBlBkRGVarIDCDAjKUYFYEcgHIFfBAqMA/AAwUYBgowDA
+MFH4Ao5AMFGAYKAGCjBRgAUYB9QDBRgowUBRgoCgKOfkCjBQFAUYBgGCgSjBQFGBAtVhKAQFVgow
+UYKjBQFJkFGVK0zIY1JksY12nZ/b3de9Z4ztNvnltvqY6epuIxmcMbTx+KjioNTuO74+HH5s9Z4O
+69u9m7rvevHrn03rt5Y+/wAZ8n0J7e+yvtva6OlG60J3e71Ih6mvN2/DCIS8Op807v8AuTuNs59O
+fTj5fxfTO29n7HtcfyY5M48c7dfwz0/Bm21+zfY8oiNPt2lhE87aeGnHzhT+J0O/9xc2PHfP35c+
+e77Xj8OPT7Ncfwao+0/Y9GYx0Zwx1ukaWhGpmv8Ah4RMMn/nubbxs+eWxx+4aa9ccWuPuw4u4+zv
+bM8c51djqamFuWrjpacTM/CYmZ8Tl0/uHkx4bYx9Vyu3f9vydNtdM/Zf3Oj1fs1sHlp7XseGtqy4
+mMMbwp5OJnh8sjsdf7i38duSYXGntsuePjxj564/gxzf/bafamWp3jW9r6+psNFau40dvOnvMscN
+OJmVhhNpjL8OZ2fF7z/u5x45sY2z0xnN18fnno4dOD2zgxtycemM5lmNbnpj+nXPj9WL1eW7jseW
+Gx7z3HU2WfbZ7h/5fYuzYaerut3nob3KYx46cTGnTh/3Jh+Hj7Xj7rX9Xi0xtjf0dOTe411uvj4/
+zX5X6/h8027Xkz23c7/o50zy3PFrM7bdfC6630Z/ZZnwufSPZ32U90912ntXvfYe4RG519plvNfd
+7nZZ6Gj2re7bLGm21I1Zxz1frfm08stLH8q8JPK+5/3L2/Hvz8fLr09Umu1zya7X82J019PTbGNs
+9frwvZ9ty9txcfT0768evpztjF12xMzOuc+qZxnfXbwkzPHD1ra+2fc+/wBrp7nu/tvLYb/Jxutl
+t93o6mlhETwjHWxeWGE/3lpzqeDPEcnedvx7Z14+X1a48Ns65xn7dfPPw6+n4vo3be64zxfn2113
++3bF+uY9X4Yc/S9qbnTxnHDa4TGLjLTwWEYuIiVgpleeUzlPWTV27/XPjn6fX/CY+GGz/wCR089v
+p9Ph0+Dh7v2rOvhMfSzy4TfDT0cJr6y+Bz8ffenPjj78trj9wxr54+3LEu8/b/b73DOM9HVyUTxx
+x0558FOPOfNHddt7ttpnEzj8W9nueLmx6eTGucZ+LyX3L9qtbRnPW7bnjePzfTmPpzMTy/LPKfge
+z7L33Gem/wDF5D3L+z+HuMZ37bPo2/y/0/T7nmW82e77duMtrvNLLR1sOeOcL5wer4+TXk19Wubh
+8q7vtObtOTPHy6512x8f3NmJZm1cZamRlQLVYWjIUC0KBEGCtTDMZAYKMA/EAwDAMAygyIMKRKCD
+6hUZUGBWBH0Cj8wgwDAMAwDAP0BVYEYBgH4ApEyCj6AoCjBQFAUBQFADAjBVYKAoCgKAoCgBgoCg
+KMACj9AUYKBKMLQFRhFYWgACMJVAjBVCowlUFRgowUYKrAjBSZBRgAUYKAqgqMCsFQFAUBRgoCgA
+ACgKAoCnIJRhaMFGA/AFADBToEowUYKBaBBgoAKUIUBSZBQFClGCjIUkFJKUBQhQpQFGCgKMFCFC
+lCFCgAYKMACjAAoCgSoAYWqwUBRhKBRhEfqBWCoAYBgGAcgo4AP0AMA4AAAUfQB8QDAMBEgH6gH/
+AGgqMJUmSsa0zkWMa9B+3v243HufX0t7v8Mse2zNsNKPy5asY85f93Dz6nm/dveNe1xnXT+b9n+L
+2/sX9vfr645+46cfljz2/wAP2vrD2l7M2vb9rpaelhjpaeMUjS08Fhhj/wAOMTwc9fHqfG+/9x25
+Ns5zmva913uumPRpjGNdfB6B27sG10McdPb6WOnjDvpYTGGM265Ln8IPOcvdbbZ65v08nmufvd9u
+u2b8/wCDusOwbTLSmNxM6vCLznqZxhOOPHplCRw45dvHHR1me93xn8vT7MOZtvbmE7jT2+00tSNz
+qzGU6GGrqY4Y6c/38sbT/wDTwOTX175njnPyw1+Tv8+nO22cTHnMePwx0/F2ev7L2eloZZ5xnqxh
+wy3Wec4Z6mU84xiWog5duLbXF8vurR0923ztMTF8p4fW4efYNKmOGGX0NHDlhGGEOPHnz9Dg9OfH
+LZx3ubc9c/Xlxf8A1fQWOthjjjrN6ec4ZRDnqcmPVPFz/wDkdvDPh9bl6Xt3LRwmcdWcctTjqTGE
+ROXXxx4eUFzpnLg2771Z8PD5/wDq5Wj2DDVyxnU1NTP+9wmszHTlHBGWOJr7d7nHhjBuPamOrllu
+NDHKN1pxPHGfzTE9J4cfnBnni2mcYXT3KY9O38uXC1ux6S09TVjLT1lW+MxE25zjKiImTi9PRsa9
+5nrjHXDgbnsGlrzjhra2X5oWnlEYY4zqebjh8iYz6c9G5x97nXrjH7fBj/dPZu6xwyjT3utlqY5f
+4mOvpbfV1IwTro5xhjMf/wAljb07rXXx1x9+2Mfbi/sjtu2910znrrifLO2Mf9WL/wDpjz/3D7X3
+G50Ms9HuGknGEaupssJyh/3c4xyiuT68ju+077XXaZ1z/wB37HrOy9w11zM6Z/78/h8cPF/fnsDc
+b7bakZY6M46GpGE7nSjPTzwymIV8NScoxiZ6xkp6Hu/a/ddePbHj1x4Zmfuzj6Ydh7l2XB7twY4t
+8+nbx1z0z92fP54eEd27Rvey7rLbbzBKVjn0y+B9D4O405tfVq+H+6e08/t/L6OXHTy28suFEnO6
+nGVZGVUjJWwowhw+IBgGFVkZUAAGCgKMFAlAtGCgAJTqFPMJQAChQIUBR+gKFKEKApzKDIHQAwUA
+AowUYKMFPh6goCjKUaAMCAqgGA5AowDBQFAABkQ4fIojCqwVGgUCDC0BVCICqABU+AFBRgqAoAmQ
+UYB8AUfgBXHMFRgowUYAAwDBRgH1ASAkAwUYBrmA5AJAPxBVYEjwAPxAoEYD+GBGIKwD+QBgOAQ4
+gI8gowD/AKBB8Apz6BEfiFWJYB/0CDUARhR/0CKwD6sCMA/46gV+vmBHADjzYBgGAYCZkoP0IDKD
+IDKD4AGuAB8f9gB8SAwDf+8oP18SA+v4lB8QDATP9sAHw8AD9AD/AIgAwET5cQD4/wC0AwHw+YB+
+PEAwI/EBYQVgGAfqBHxAOQDAr8eIB9QDlfEA1AQcgH5hUYBhBz0+YKfxIKkyVjWmZKxzllHsX2vn
+7k7rjGrjM7HQmMtWIj9c9MI+J1HunfY7bi6fzZ+leo/t72n/AHvN6t//AK9PH5/L+PyfYPsr25ob
+LbY5RpcIjHGKxyr/AMPl5HxL3LvM77eL6V33czGNdemHp3b9rEYRj+n8vFOIjF+PieT5d+ryPPyd
+ayLa6Gjo0mcYnLT/ADac5REzjOULhK5zHU0/Vmuo5N9tr82/nrZaepTCI1NW0fSXHTcxwymOq8PE
+z12cWNLi56Y8/i7ztm4/Z7SmnNdzqzbUzz45TlZTNon1Nrj5fTr08cur7jT9Te58Mfw+Dn73u2lu
+dbDRw/NtNrjEqYV9RcMlJt9x3mOT04/p0x+PxanD22dNc5z/ADbfhhtaGE77V09vhnGGepNdTVzU
+44zlxlRHGZnp0MOH/U3xrZcy5Z75/TxnbPl5Ow/yvS1u55bfQcbbRxiM9XUm055RD4ceB2H+0127
+jPHx5/Lr/VnzmGp/uc68Xq28c+Xwa8u0auOU/Tn6iUYwlMPlz4GOe22sx1/xY47rGcdejsNDtOnp
+xnlq6jyiFTHrMc5X+7gdnx9hrrjbO+3XHljxz/HGGnv3Oc+GG3raujtoxnGY/JGWnWYjLLKYhQ/F
+mpvya6eGfDp8Wemud/2ug7hjhoZau2+lbVrfDTamcJnhM+Mw0dTyZ9Gc4jtuDOdsY2vRju4ynDHU
+wmYmMJq+EyvhymYNHbd3PHi5xlxdxudLT0scbNVrlH6qxwb8IMPVfBz6cec5YP7jwjT1895pY/4m
+f5NTFwtTGecLjD6wdr2m3qx6c/8Ao9R2GbrjXPhj8GBb+NLV1MZmMIjLTyw09SIiL6fXCYX5o/5f
+Q9DxXGPt/H4vU8Oc64+38fi8j9+e0NlvtHPTxwqomcYjjOD5THlB7X2v3DfTNb/d9pxe48GeLl+z
+Pwy+f+47DX7bu89puIWWE/ly6THSYPpHDy45NcbYfAvcew5Oy588XJ44/Fxok5WhjLUyMqpFo5+Q
+UAAowVqc/wBhGVGAfUAwhP4hRgHAQfoFHARGAYFfUKNSEH4gRgIkKrgIj9QDAP1AWEBlBkB+oBgH
+/UA/UAwDKD8eQBgGQGUGQH48ig/MAyAygBGBWAYCJAR58gI/EABXIB+ABgG/94EfoBWAf8cgD/hA
+GAYEcgVsCMBMgowHwAfAA/UABXPiEQLRhD4hRgGEGuXMCsCRPiAfUABXIEBRgoA844AAU/kCnwBT
+4gAUBRgJmeoKeQKMFOgKcf7QAAFAUBQFAUBQFGCgKfgEAtAUCUBSQUAAoCnwBQFP5AoCoCqCgKeQ
+KAp/MFAUBToCgKfzBQFAUBTgCgKAoCgKBKcAtAUCUBT+QKAoCjC0CUBSQUBT+QWgSgKAoCgKAoCn
+xBQFAVCpUDHOUwwz1tTHR04tnnMY44x1meEFznGMXJprnfbGuPHL6e+1XtfHtnb9vGWP+LnETlPJ
+5Z8pmeh8m99779Xkz8H3Lse117LtdeLHj45+vzfQvadtGho4tZZx+THKEpmOsT0k+ac+/qy6HueT
+1bMj2WOOGEZzCjGFnwlqOc4xHE6vkzcum5s5zmN7W7lobeJ1dT/t48MaxbJ5cI9TDXhzt0w49e32
+26Y8XmHuL7id13Hf9P297LjDW7js9bHU7xvc8Yy0sacY2uKiXlklnlH6fjy9Z2ftPHrw55u56a7Y
+/Jjz/wCf6seWPN6vsPauLPHnk7jppJjHn/zfvx+L1Dt3fct7stHd5bWdnra2K3Ox1nGWjq9ceURO
+PhlHODy/Pw+jfOuM+rHlnHhnH08nk+fs8cfJnX1erGPDbH9WP4/J2e13+E45RqZf4kT1XGPj/I1s
+4zho8nBm9PBv6Hef2GrG6wWUYRL4xH5p5S5mJn5F49t8bYzjxce/afq49OfNq7X7qy22etnpzjlr
+6kzlhp5ysVOVlj15m1xc3LwZ9Wvn/Gse49t9eMYz4YZBtPeMVnHV0sNLUmZynKMvyvLjM445OZ8+
+Jt6+68mPDE65+rr49M3rn658nUcvtXXpm4cLW91aurqamMOsTOOGrhlXFZdFMuUau/ecm2bnPX9z
+a09txrjH7GBe6/uxsOybvDsfZNHL3B7r10tht85ww0dKVGWe51YifpwuURFpO27L2/k5tM8u+f0+
+PH9WfP5a46X8MY/B6H2/2LbuPzcmf0uPHjnP7sebk7D3/tO86WURhnsO47XHHHfdt1f+7pzM/qiZ
+/Xi/05R+Bo912nJx5xOuufDbHhn+Gfk5Ob2Tft8+ON9Nv5d8eGfl8s/HDVr932+eeU6WeU4zMVnF
+RlOS4x5eZp44dvNjp2u2MdXV7ruOM4LUynDTji+E58PPw+Bs6cPXo3uPg69PFj+v3TR1Ms8Y1MYy
+nllKlyuMceUrgdlrwZxjwdrpwZwwvveGGdsNOY0J1ZvpzMqfq9Jw5Qp/vHfdtnOOues/Z83dcW+c
+Y+LF9xh/mmymJrG70LYuIUTlPOPhPgdtpn9Lf5Zb/Hy5xl4r9wux6erp5bnRwrqaLT5qOeM/A957
+T3WcZ9OfN57+5+x17rt/Xj+fTr9nweWxPieufGa1wYs8ZULVIoAAgKoZjfMFAHEFAUBQFAUCUBQL
+TqCn8wUAAAlPiCjYWnIFPMJQFAUBQFAU6MFOQKAoCgKAp+AKAoCgKAoCgKgKoKAoCgKAoCgKAoCg
+KcQUCUBTiCnAFOQKAoCgKApzBTqCgKAAU5goCnmCoCqCgKAoCjBQFAUBT4AqBKoWn8cAVP5BFC05
+gowlQpV/kQqAUFAUBUYFBRgQpVIUYAFOoKjBVYKAQFAUBVAgKPh5gClAUIUBRlAhVYEZSjBQAyAU
+oChEoyqMFGCgSjCnUiH8wBQIBSjRCjKDAMACjBRkBlD4kBlAAAfqABQFGAZChQYBgowU48wAAACj
+AMAwVJkMa0zJWOcsl9hdonvHuHb6Ux/h4TbKfD+IOr907j9Hgzl6n+2e0/W7vG2fDTq+w/aHa89H
+PTw0ojLV1VhERx5+vSOh8Q9w58ZxnOfDD6Z3nNj05znww9G2ejjjqTqy509Gf22GopX1I45Lo58/
+A8xybXE+PV5rl3uJ55/N9jsNLcfTcV4Y4xMYW/Vn0nlx+BrbaVp7cdeZ/cr3zve26O37N2PKI773
+TLLS0G1o6cRN9aInrjH6fM9X7N7ZpyZzycv8mnXPz+Gv2+b0Pt/Y42zdsdE+1mOz9vdt0N5s8NDW
+ymdSMsd1GV89fHUU631MZctcl1Mve9tufkzrtfLw8JP5Y3PeuD15/RucYxOuPhnHhM/tey+w/e3t
+r7p6Hdexamnpdv7zsd5r7Pa6sTGX1MtHHHKM4iZcxlGUvF8azyNHT2bHHvrx7/l/V1xtrt5erPT0
+7fX8fnjz8fm3uHZdx7bnXn0znfjn5vl18P2TPlcOn7hp73tW51dnu8PpbrQzy0s9CJmZvzT4OJiY
+ywnrB1PJ2+3HvnTfGcba+Nd5wbcfNpjfXN1zi35fTpn5ul3ndNbRziMY/JisJwy04nLKOvGH4nLx
+8GM4/wAXYcfBrnH+LTodx0dPCMscpjVyymmWOS448Hk+Jlvw5zn5Mt+LOc/JzdTu+OFJznUyzw5Y
+uZmf+ZzETx8INfHb34NfXtr4Ri3f/dnfd73bZez/AGfoYz7v7rE115U6ez2/LPXzx4w1+j1+Pc9n
+7dxa8e3Pz5/0tPLz2z5a4/e2tO14+PTPLy5/09PH5/L/AAese0vbH26+zu20tv3bdTq+4d1hG93+
+/wBbR1NxuNfPVnKJ1JnDHLrGShm3tv8A7jk05Oe41mPTprj8uuvljFzjDxfd9x7h7vf0df8ATxn0
+4x6sa46fXnHyafc2h7Q+42nv8/Z2vp5e8Oy6GG60sstLPb5V3F4w0tWcsYmcNSdPKJjpwnwOu5Nt
+eDa49WOLfOfGeUucTbOLrcT4/e2vbuXvPattde6xn9Dlz6c49WNvCfmxM+OL0z5+Dxba+443OnGW
+f1NCMJz0txs8/wAmpobnTlTjlPXKJhecG/v2fpz5Z88Z+Or6Fy9vjTOcdM/DPx1z4Zx8stGv3v62
+ll+43FnPDLBzWsqWoiOvFsy17b056YcGNca56YdXrd6w28fVnPPPX04nOVjjnjET+mYXGeHE29e2
+zt08mdvk6bu/do1Yx085x1YnGXq3icXn1hNfP0N7t+CdfByabR0M77H6UauhFc4bzdVnjC4xHBSd
+jji6zLlxyMQ9xa2OtKyxv9XF5xPWYjjlH+07vtNc6/YyzyYz08svEu57edpvtbR5RGUr4TxPe8O/
+r0xl8Z9z7f8AQ7jbXyrjRkc0ddhqZGVGRQAwDA1NkZAEBVBQFAAKAP4YAFQC/wAwHMCApz+AF5cQ
+HAACj/qABU6go4APoUUhUYAFV+AEiQUYKFFf9SAwUBR+AKfAFT8CoMLRhALV5kRGVT4BAhQoEKfE
+oQAYBgGAAcAAAFPiAAAGAAAowDAfxxAAowAKMAABRgAH4AowD4AGCgBgPjwBQIMKj8AK/UCP+JAv
+EJRhQFAgwD/qAAjgKdQgCgBwBQAE/hAGBWBAAFYEYD5AVgT5AHxAMAAmUA/hAOYB9ADAMAAf8QAf
+8dQHAAwABgGAiegBgJkAwDARz8wACwgPr0APxAPw4gIkAwI/HkUV8CB5xzKD9SCFD+QBwBWuXyII
+/EoMIMKrAP8AAiI5KquJIifyKHMA/IKBB+HoAYBgRhjlpmTJhl6/9lO05Z7jV32WMKZrbhPCPzTE
+vxR4n+5Oea41fUv7W7f9PttuTPjtl9m/bfY6G1z1+87yI+l2rZZ6881+41uUxz4xEfpPiPunJ6vy
+Y/qz+GPD8c37HH71y7ba68WvjybYx9mGT9y2OHavZfasdxlGr3DV3H1c8sInCM9XWwyynOMZmZ8Z
+U8jrOPbHJzbZ16eOP+nHpnV1Pb82eXvd501xrPsxnHR5tve85Y6WernqTGhjOWUrLjTHr5Oeh6Dj
+7frMY6vWaceMeTzieybDvW+/znvOpr7juO8xy0dLa6OWO30dHQiXjOGUWztwczY9R/ud+HT9Pjxj
+GuvW565zn5+Tt9e4zxdNfL7ervttu9tsdDS2O0zw/bbeZ+nOf+NMTPGYl15ydfvx7b7Z228c/Y1+
+bl25ds77eOV9qbzZe19/Pde3aOWfcdfd5dwy1MtfLHGdbKrrhjjiseHKZky7vHJz4xrnaY1xjXEx
+1xPDr8fucPPrjm4s8W2fy5xPD+LNPeH3S3XujcbLcZ7TT2O72uGelq7nb5TqTuNPKHjGcTjFfpz+
+bGbTznxOHk4t+fGP1ZtnGPH0zbP/ADZufVmdPDHTxsw6T272jj7PXbXG2dtds4zM/wBPxn1+f1YY
+Vre5tDVziNbWmMsMsqZ/UnjM9Iq2zk17LbGOmPwdx6MYTDvs6Ov9bPVjPUxjhiphwnGUTPOH0Ge1
+9WsxhlnW4bGt7n7nnhr47WI+pNpidWcZwnNPGfGYiecI5Ney48Zx6k1118219r/de49i993ffO57
+KO9dz3k33O51teNPLBclljjMRhj0xjE2Pde117rTTTTPo108NZmZ/HGb9rj9y7bXvOD9HG+ePH1X
++Dv/AHv9z9x707vp941NphsNGNrhscdppbidxp4/T1c841vqfTw45RnOMxOPAme1sxMdJ4YnTGJJ
+nbb6/Fr+2e38fYcH6Wu2d/zZ29WcTxxjElz0xPi1/br7l9n9ke6e69375huNbt/ddno7T6O1wxy1
+NLPb6mpnGc4zMOJjUmFzNbu/bdufh101xc6+r+qfzY1+WfCf49Gv7x7dv3vDrrptjXOu162eHXri
+/Jjfv73V7W7p7n3Pe/bO5+ts97hjlq9v19LU225y1o4fUweMYzljHqbHtvY8/FwY4+XE9Oem1xti
+fDLtewztp2mvDzZ/1NLM464zr8GI59/0nTCY1a8JzrGGXzrPQ7jHa5+ozs4WPf5x3GOf5cdK05Wz
+crqo8n/M589r+X5sM7uJuu9ZakWwwx0tKZnjh+WJjxyiWjn4+2nj1ywzyTq6ye55aeUY55Xwy4zl
+jMuMekTBtfoXwX9Xq6/vO40s9N45/picsc48Z4rh5Gz22mcZY7crzP3VhE7rS3GMKNTGYmfGcZ/t
+PV9jn8ucfB4X+4tMevTfGPHE+n4uhiTsXlcNTIyamuRFRvmFOAFcIDURUfoAYB/OQKwJ8AD6AGAY
+BgGAYBgGAYBgGAYBoA4APiAYBwAfoAfoAiQDAMAAiQDAP5SAmeKAPzATPyAMA+ICJAjKLMkBgGBJ
+nwKLEkEifkUV/wAQQHwAPxKg+oUZAfgBGUGBXHyIDAPiERlFcEEZQYB+IUc8+gB+PLwCDAMKRPiE
+GAcgH4AH/QAwHEA2AiQDAMCOQKwDAMB8wDAjAszIBgInwAjArAAH/EgGAmQD8/kAcgT+YAAwEAAK
+/MCAADAAIlAAUfUAwK+gEAoD+QEAMAwUAMIBaAAgAAAGAAMACgAFAD6FKMhRyAAFB+pAAAowDkFA
+UAMFGChSgKAoAAMhQFABQAgKAUAyFQqVJkrHOWnnMRHOeBWL6Q+0O00dDsWnERGGpr/l1L8q8H64
+zJ8t/uDkztzZ+T7Z7Zx44+z49cY8v8X0j7f7rjs+27vY4a30sO4brR09LOMLY546ccIfTj5M+Xd3
+wZ33xtL6dc5y6ru+D18mu+cX0Yz+LIPfXuGM+3zqbSInDR0sNvt89T+5lqTOm48Z6mn7f2v58Y28
+fHP2dXTe29tnTbr45zcvGvem7nZfU7NoZzGpH09tmqxMTwieU8Jn4nsvbeP1zkz88vVcO3qx6suj
+7l3THZ4x9GcsNDQwjSwmZi0yuULj8ZOx4eD1+PjnqudmLanfc8Yy1fqY4TjLicpmax48Op2+va48
+IwzvPNuaPuLKMIbiIiMonHpPPjE+PMw27Tqv6jlT37Pcx9HCcctDJTqTk18HjxZw/wC1xr1z4md6
+4257pttvrTuN3qrb6eM21csYmccYjhjEREc+RzacG22Jrjrlnr8cu8jsfvTa+z495b7t049k19TC
+Yxzictba6GvK0dSf+TOeEvlPxND/AHHa79znt9Nvz64+zbOPHH14beebtv8A6vV/rYxZ8Z46/wDN
+rjr9/wAGObnuOtnjjuM9OMc8/wAmWWEY1icevDjGR2WnDjHStXbPxcPPX188omM8p08pdn4f8XwN
+jGuuGNbun3GdOJxvjGMxN9SFFZnqYZ4amN3Gy7rqafH6t8MeWMp18YficmODGfJP1Jmurz7lE62W
+aytPHGZmXEwbmOHpHDnlznNaNTuGWWUTEPKP1TKcz/sMscUceeSuHrbiIiYmbTPGMH1ObXRx7bxw
+I7hlln9OcpxxiZtipyUx0Nn9LpWvjk6zybervbvLGeGEvGHxnxmDLXijL9TGfDycfX3meeCy4TMK
+0RyienzOTXjxjLDbkzhinfFntccv/wArU4T/ANX5V+J3Pa9Nvrw837vj1dvjP+Xb/B0cSdg8ljLU
+Ys8ZVhlQAwAFIyOfGQDAAVgQFALwAP5ARgHxAAGAYF+QE5AGBfgBGABTiAc/MIAAtGEoFGEGFAgw
+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
+lYw5nlAVzsu1952m6x0MtnuNLduJww+nnGc+E4qOPyMMb6bYtxHJtx767TOM4y+oPsl9we9732nr
+dt7pnlnuO3av0NHPVhZZaXD8uUzEzM4zKPmH9ye16a82OTTw28fr+L6F/b3NnuNPRydc6+GXrG13
+nc88f3GplEzg5+nOUZwso5Qjxm3b6Z6PT8nDpjo3O8+3Nn7r7Dnh3X/yNHS/72jGMRq4uOeL4xMM
+vbc2/ac116fsaO2Zv6JPV4Z8nhmP2U32v7a18dtEaXedhra37eYc6e72traUan/BqRxiJjgk/E9z
+v/cfFjn1xn+XbGLn/Lt5/Xhp69tyceM4z8emL5fTo8sw1M9LKdPWxnT1tPJamnk4yxyxmYmJjyk9
+LnW9ceDDHJXH3Gt/5enqOVMzz8JOTTX8ucNLm5Jy6bfTq0Y7rLTzrM/lj1UGWdMZwy155tMuRhvm
+9LNZ6Ocfnwnjy5fGYOPPF5+bb17jGczyeo/br/UH9wvtzp6Gw2O+/wAz9u4ZKO3b6J1p0Yng9POZ
+vGMf8LPMe5f212nebZ3n6fJ/m16X68eF+Zydv2vdbY/3GPVjPjtj+fGPj/xT/i6zwywPuvc/827l
+3DueeeOpnvt3rbrPKIrFtabTw6cZO74OD9Lj10x/TrjH3Oz7nbjzy7ejN0s1+rGI+h/9Iuxx1+6+
+6e45w8YnYbLTmePGM51co+Cx4nzr++d5pwa/8237nRe6bzinzz+Gv/8AJ9X7/uEYxqThnOepMzEY
+8ofLnJ8uteU4eCy4mHAy0Nr3jtmt2vuu20u5baco1MtrvNLHW074S8ZrnExKnkZa8m/HnG2mc65+
+OMzP4NnO23DyY5OPOdM+F1zOn2Oq0vbXtvQ0dTU0e07Hbdyc/T1Y2WjMcPGKo5s91zZ6Z32zj/my
+3v8Ae9xcY/U3zp8PXl1+tobnaaGO5wUbe9c8NLSx08dHLLksdOEp8V8S4zjfPz/a3tN9d9vTnxnn
+nOb97lbH3Z27sO21e9993ulseyaUTff7nOMdNx004555eWLMf9pyc2+OPj1ztvnyx4/4fa1O67Hb
+l/0+PF3+GPLHx2z/AE4+t4v73+/3uv7kb3P2x9rNtuO2e3dWctHW9w6mGWnuNZ8/oOJ+nC/vKcuv
+A+hdl/bPB2XHjn77OM7Y644/HXH/ADf5s/Lwdh7b7Vwdv+fk/wBTfH/Zr9/8315/L8MbdMvIvuj2
+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
+m52z/wBuJ+99c/b725sPZfac+0bDb7XZbfTikY7bSjDVyo/z6mUzOWeU84mT5Z3/AH2/dcmdt9s+
+Px/d5OXk4+O49GM/a7vfa2Hccdx2vXiM9tGETqy/zZTl+jGJiPDjl8og0c4zx7+rT+a/lz+9nrrO
+v0+586bn7O+zfbnd977i3upqd07l+5nc447rKKY6mplOcflxX6T2/J733XNx40xNcSdPH6Zd12ft
+/HvnGZnN/Bj+z7R717P3Tune5z1u5eytxucv22eEzuNXR1pjH/DyjH8+ETjPCZxjHkb3Pv2/c8Gm
+J6eXXE8pludrx78PdbaZ319O12men3Xpn54rJOyb7b7zU3G00NXCdxqV1NXaYfl11pzE5uJiJi0P
+p4dZOi5uLfXGM5+/ydxz8uJc9Z5ux3eh3TZ6OjtdnnjH6vo6sKM8lMy11iYJwcmNrnPROPn022zn
+OKx3uO87xExlqa+jpYzEPPQwzxeMypcwo5fqZ2fHx6bZ+Lc/T4t9M69evxz4fU6z3Dj27um2jaRv
+s9LumzWpo6O4lY5Y6uPDnVtc+h3PDvjXW+WXmccfNwcnXW4+X4PJt/37Q0tTV0dSjxyyx1YzzjDG
+MsZ8cZzyyhx/djid1x9rtnGM4+n7GHN7j6sZ1xcXx+nX72Zdg2nt/f8AYs88Pc+1ju2enluMthj2
+vffR0+HPPXxjG0+M0kw5eL05xnOM9Pnho7dvtnHXbPXw8c9fq/a6PX9h977vtdTuew7h2zuex01p
+av7DUynU0uMT/iYaqzw87Ywc+ebTix+bGcfX/g4OHh5c7ZmvT8en4uJ3v27tO390nZ/W1dxo5Yaf
+5JrE41xiMcXx4REI1eHvc8mvqxjzen7fsP1OPHq6X6fb9Pm4/ddnq6uhpaWGp+6x0Jx0NPSyi9HH
+5azEdKxyyNvt+6zi3o0u89uz6tca5uu2et8sfHHwad/7g93bv2po+0M9xlt+06OvlutPPHHGZnUy
+iYmzUqPzJ8pmTDh4e307jPNnF2ziHe9nzcvH/pbenMnl18/s+ldn233R7l7b2fT7ZhuoyxxlRvJx
+xjdac8OMZTDxj4KevM4efsO35OX9TOs+Xk0OHvO500xptv6vL4ff8vnl0HcdPW3+pnnvtxq7zXc4
+5a24znWy5zP6sp81zNjTONOmuMa4+XR3PBx+vi69c/f9Pg63T2n0NS2nGOWlaYzjPkvPylHPnkrD
+k7bXbXOM46Z8WQ/V9sftrfttP6yp9P8Abadv0urXh/ebtx5cDSnN6vHM+vP0/wAHH/t+3np/Tx/2
+4+Hx8ZOvx9XXw6PHn5nrnzhWEHHiBPOeQUcAX+YE+IBlQaIHUKFQZA+HABADoVRkQfUocOpAZVGE
+ORAclB/ABzAMijKgwD+RAfAoP+oAgFBkDnBQmfQAwDkAwDARIBgADnqA+YB/MBzAMAwHmAjkAAnE
+CsAAAATmBeYEYF+AEArAgFYEAAX4gRgX+QEYF4gGBAgFUJU+AUAoEAAAHwCEhT4gAgFAH4hD4hRg
+AgBQoEQKBAAAfkFAgAAAAH8MAA+AAACgAACgAAAAAPIAAAAAAAAAAAAAAACgAJQAFAAAIAAUCgAF
+WImZUc/ACBAACgAABu6G519rq46+21c9HWx/Tnp5TjlHzgx21xtiZxWenJtpm65mfkzb259zu7dq
+1Pp90e/2mUK35cdbDzjKYU/CTqe49s4+TH5fy5/B6723+6u67XM5P9XX4Z8fsy9W9s+6dr7j0stz
+sNxhjGCx1NHJYbjTiedseU/GDzPddntw9NsfwfR/b/7i7bu9bjOddvhnxZho62w08M8txvPyYw8c
+VNpfDl5HT7a8l6au3x3/ABZxNc9W/O+22Orjp6OcxpZYROnnKico5xPHlE8oOD07Z65bOuc5xfwb
+213V9SfqakRpL/uyomseE8uBhyZxjHTDDlmMdWn/ADXR2ncMc9j3CNjOpnMZ5amnOpoTGUcbxhjK
+fXLGCen9TWb638Muo3300xPTnPn0bG6y1t9uNfbb/CM9acZyznHK2nljEcIeEquXTKJNjXTXXXGd
+fBvY07fn0xjHXH4/b5sGjt+ns9x9DZY02ueWWpGjlxnK0xCxmfBHdY5M7a/m8XX8XYY7bkz6M59O
+3llyNbtEamWEaf8AgznlaVlaIcdIjy5F15M+fU5tMZzcdM1rnYaunjTQznDQz4T9WIrnnjMRz4Su
+POZLnfHnj7mtrx5znOMbZx9fn9PrcXue1jVwyy166VcHCl4ZVl/KY8xxbTPRhz7a63O+cYxPp9Xy
+cXZbbW2+E/XwmZy/N9SOOE24xxj/AHnNyb4znoy7e+nr5/c7DSy1NDUjcxqTpaWi9TPcTNcYxxj8
+zmOcRHM456umPFy8/Jx8emdt89MfFzPaX3K9o7Lebnt/ce47XX225/L/AORhuNHa5ZRMJ5/S1V/1
+I3+X2/n21xdbPn1eR193498zGfTnyz4Ox1e8dsnumX+W57XX0taZzxjt25y3mGnhEq2cVxnGIacw
+dX3Pab6aXP5fr8/qeg7b3TTXOPXt6r9V+vo19x9ydt7ft9SN3uccNXTxnONDC31dR/piIlQp8fma
+vD2vJyZ/Ljp8fLDY7r3ntuLTOfV1+Hnn5L7Q3Pfe4rf+4dDPDSxzwjtGlm8NHT0s4+q1jDnhFrTx
+4HH3+vDw/l4uuf6s+fw+mHn+w5+fuc7b8txjMmJjwdfvu0bPe6e73G908tbcZTnMa2OWWLyymPzR
+HKeMDt+75OLOMau5732/h7v0+rOcYxjyz93T6R2X2v3HuH2tsN5p9s3ez2+ptdzfTw1tLLKcscol
+OYj82Mzbh0lrmZ+68nF3Xpzm+HV4rPtu/bYzpvj1a58Jn9z2v2z37u/eu2Z9y77noZ5bfLKPrbbH
+LS08p01OWUxnlPKJ5uOTR8+77g49OTGvHfDz6sduLGm3pxjxbu+7pG72U57bWx1NtjOOrq447jLb
+5Z4xx4ZYY5TjyibVObtu13tz4Y+ng2ePjzjaZx+FYz7c9xdh7/3DU0+6Z222llMztp1JnLKcVEZR
+nnjjnOHCfP4co7LuOPbimcY+5t8/63Dx4xp/N8f8GZf+7e2+xaE9v7Hp7fT22OUzOEfV1pnOeczM
+Y5ekyaf6e+/XOHRbdtzc23r5LnP2MT99Z7T3r2v/ADDs+3z7b7v2GUbnZb/baWeP1Yxj82nnKxme
+EOG+MR0mYOy7Tfbi29O/5tM9M4+DPixt2+0zm658vh9Pk8v3H3A7r27t2c952Wj3PueP+HttbRjL
+bYzmph6tZnl/+nEOenU7f/x3HycmPRtnXXz8/u/xd72XqznOPCfjj6ebpfr/AHM7hp6m83G40NGc
+4xyx2Wphp2zif0xlhMZzEypTyiTYzjsuLpjGc/O/vdl69PB0PeMu7d009DV19nGy3Ns9DV08cZrE
+4Z8YxnLj18fmb/Fnj0z0zceOHNx7eu4uMZv3NOOt2T25pT3HvPtfZbjdxM5aW83GtnOnjHRaET+f
+JxwiZ4vjyN7tuXPLjOmu+c/KfvdT3vH+nyY2x6dMdc7b56yfDXzznyw7DtfuT3P7n1NTuffNDHS2
+GrttTS7Vt4jT0cMdGfyxEYxHCJXDpw84OP3HOnHrji0z1uM7fT5NX2n/AP6eX9X07Z0xn8uc+efp
+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
+qNqvM47tfA/Vx6bM/VP3eL//2Q==
+
+--------------Boundary-00=_S8LEXFP0000000000000
+Content-Type: image/gif;
+ name="stampa_girl_line_de.gif"
+Content-Transfer-Encoding: base64
+Content-ID: <F0E30D5E-C01C-4CC4-BCED-F05ADA8739AC>
+
+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
+BkEAADs=
+
+--------------Boundary-00=_S8LEXFP0000000000000--
+
+
diff --git a/comm/mailnews/local/test/unit/data/message1.eml b/comm/mailnews/local/test/unit/data/message1.eml new file mode 100644 index 0000000000..e82610ebc7 --- /dev/null +++ b/comm/mailnews/local/test/unit/data/message1.eml @@ -0,0 +1,7 @@ +From: from@invalid.com
+To: to@invalid.com
+Subject: test mail
+
+this email is in dos format because that is what the interface requires
+
+test message
diff --git a/comm/mailnews/local/test/unit/data/message2.eml b/comm/mailnews/local/test/unit/data/message2.eml new file mode 100644 index 0000000000..9574d946aa --- /dev/null +++ b/comm/mailnews/local/test/unit/data/message2.eml @@ -0,0 +1,9 @@ +From: from@invalid.com
+To: to@invalid.com
+Message-ID: <abc@def.org>
+cc: dupemail@test.com
+Subject: test mail
+
+this email is in dos format because that is what the interface requires
+
+test message
diff --git a/comm/mailnews/local/test/unit/data/message3.eml b/comm/mailnews/local/test/unit/data/message3.eml new file mode 100644 index 0000000000..92722046f9 --- /dev/null +++ b/comm/mailnews/local/test/unit/data/message3.eml @@ -0,0 +1,7 @@ +From: from@invalid.com
+To: to@invalid.com
+Subject: test mail 2
+
+this email is in dos format because that is what the interface requires
+
+test message 2
diff --git a/comm/mailnews/local/test/unit/data/movemailspool b/comm/mailnews/local/test/unit/data/movemailspool new file mode 100644 index 0000000000..1fb0587c49 --- /dev/null +++ b/comm/mailnews/local/test/unit/data/movemailspool @@ -0,0 +1,169 @@ +From - Tue Oct 02 00:26:47 2007 +X-Account-Key: account2 +X-UIDL: UID18345-1161858178 +X-Mozilla-Status: 0001 +X-Mozilla-Status2: 00000000 +X-Mozilla-Keys: $label4 +Received: from caspiaco by host29.example.com with local-bsmtp (Exim 4.68) + (envelope-from <bugzilla-daemon@mozilla.org>) + id 1JtrbR-0001Kc-Nf + for kent@example.com; Wed, 07 May 2008 15:55:21 -0600 +X-Spam-Checker-Version: SpamAssassin 3.2.3 (2007-08-08) on + host29.example.com +X-Spam-Level: +X-Spam-Status: No, score=-2.4 required=5.0 tests=AWL,BAYES_00 autolearn=ham + version=3.2.3 +Received: from webapp01.sj.mozilla.com ([63.245.208.146] helo=webapp-out.mozilla.org) + by host29.example.com with esmtps (TLSv1:AES256-SHA:256) + (Exim 4.68) + (envelope-from <bugzilla-daemon@mozilla.org>) + id 1JtrbR-0001KT-FP + for kent@example.com; Wed, 07 May 2008 15:55:09 -0600 +Received: from mrapp51.mozilla.org (mrapp51.mozilla.org [127.0.0.1]) + by webapp-out.mozilla.org (8.13.8/8.13.8) with ESMTP id m47LtAFJ007547 + for <kent@example.com>; Wed, 7 May 2008 14:55:10 -0700 +Received: (from root@localhost) + by mrapp51.mozilla.org (8.13.8/8.13.8/Submit) id m47LtAEf007542; + Wed, 7 May 2008 14:55:10 -0700 +Date: Wed, 7 May 2008 14:55:10 -0700 +Message-Id: <200805072155.m47LtAEf007542@mrapp51.mozilla.org> +To: invalid@example.com +From: PrimaryEmail1@test.invalid +Cc: invalid@example.com +Subject: [Bug 397009] A filter will let me tag, but not untag +X-Bugzilla-Reason: None +X-Bugzilla-Type: newchanged +X-Bugzilla-Watch-Reason: QAcontact filters@mail.bugs +X-Bugzilla-Product: Core +X-Bugzilla-Component: MailNews: Filters +X-Bugzilla-Keywords: +X-Bugzilla-Severity: enhancement +X-Bugzilla-Who: bugmail@example.org +X-Bugzilla-Status: NEW +X-Bugzilla-Priority: -- +X-Bugzilla-Assigned-To: nobody@mozilla.org +X-Bugzilla-Target-Milestone: --- +X-Bugzilla-Changed-Fields: Blocks +In-Reply-To: <bug-397009-254728@https.bugzilla.mozilla.org/> +References: <bug-397009-254728@https.bugzilla.mozilla.org/> +Content-Type: text/plain; charset="UTF-8" +MIME-Version: 1.0 +X-user: ::::63.245.208.146:host29.hostmonster.com:::::: +DomainKey-Status: no signature + +Do not reply to this email. You can add comments to this bug at +https://bugzilla.mozilla.org/show_bug.cgi?id=397009 + + +Some User <bugmail@example.org> changed: + + What |Removed |Added +---------------------------------------------------------------------------- + Blocks| |432710 + + + + +-- +Configure bugmail: https://bugzilla.mozilla.org/userprefs.cgi?tab=email +------- You are receiving this mail because: ------- +You are watching the QA contact of the bug. + + +From - Sat Jun 07 04:23:42 2008 +X-Mozilla-Status: 0000 +X-Mozilla-Status2: 00000000 +X-Mozilla-Keys: +FCC: mailbox://nobody@Local%20Folders/Sent +BCC: Some User <u1@example.com>, Another Person <u2@example.com> +X-Identity-Key: id2 +Message-ID: <4849BF7B.2030800@example.com> +Date: Sat, 07 Jun 2008 04:23:42 +0530 +From: Some User <example@example.com> +X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0 +User-Agent: Thunderbird 3.0a2pre (Windows/2008060416) +MIME-Version: 1.0 +To: bugmail@example.org +Subject: Hello, did you receive my bugmail? +Content-Type: text/html; charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit + +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> + <meta http-equiv="content-type" + content="text/html; charset=ISO-8859-1"> +</head> +<body text="#000000" bgcolor="#ffffff"> +This is an HTML message. Did you receive my bugmail?<br> +<br> +Thanks, and goodbye.<br> +<br> +--<br> +Some User<br> +<br> +</body> +</html> + + +From - Tue Oct 02 00:26:47 2007 +X-UIDL: UID18345-1161858178 +X-Mozilla-Status: 0001 +X-Mozilla-Status2: 00000000 +Received: (from root@localhost) + by mrapp51.mozilla.org (8.13.8/8.13.8/Submit) id m47LtAEf007542; + Wed, 7 May 2008 14:55:10 -0700 +Date: Wed, 7 May 2008 14:55:10 -0700 +Message-Id: <200805072155.m47LtAEf007542@mrapp51.mozilla.org> +To: invalid@example.com +From: PrimaryEmail1@test.invalid +Cc: invalid@example.com +Subject: [Bug 655578] list-id filter broken +X-Bugzilla-Reason: None +X-Bugzilla-Type: newchanged +X-Bugzilla-Watch-Reason: QAcontact filters@mail.bugs +X-Bugzilla-Product: Core +X-Bugzilla-Component: MailNews: Filters +X-Bugzilla-Keywords: +X-Bugzilla-Severity: enhancement +X-Bugzilla-Who: bugmail@example.org +X-Bugzilla-Status: NEW +X-Bugzilla-Priority: -- +X-Bugzilla-Assigned-To: nobody@mozilla.org +X-Bugzilla-Target-Milestone: --- +X-Bugzilla-Changed-Fields: Blocks +In-Reply-To: <bug-397009-254728@https.bugzilla.mozilla.org/> +References: <bug-397009-254728@https.bugzilla.mozilla.org/> + <4DC2493C.4060403@gmx.invalid> + <BANLkTin2w8LJxYGHV3_5NpFbsiBhrP96XA@mail.gmail.invalid> + <175221688.20110506234025@my_localhost> + <201105072315.25120@thufir.ingo-kloecker.invalid> + <BANLkTinacQCd+mZ7fL1THLK55X2+u9g5-w@mail.gmail.invalid> + <05433510.20110507224940@my_localhost> + <4DC5C015.7050800@sixdemonbag.invalid> + <BANLkTinv7NvPG9gE1Fha+X+6ZkHzdXdRdg@mail.gmail.invalid> + <BANLkTi=6zDTsYymc+bUTwPOM2AohJD2wfA@mail.gmail.invalid> +Reply-To: FOO <bar@foo.invalid> +List-Id: Help and discussion among users of GnuPG <gnupg-users.gnupg.org> +Content-Type: text/plain; charset="UTF-8" +MIME-Version: 1.0 + +Do not reply to this email. You can add comments to this bug at +https://bugzilla.mozilla.org/show_bug.cgi?id=397009 + + +Some User <bugmail@example.org> changed: + + What |Removed |Added +---------------------------------------------------------------------------- + Blocks| |432710 + + + + +-- +Configure bugmail: https://bugzilla.mozilla.org/userprefs.cgi?tab=email +------- You are receiving this mail because: ------- +You are watching the QA contact of the bug. + + diff --git a/comm/mailnews/local/test/unit/head_maillocal.js b/comm/mailnews/local/test/unit/head_maillocal.js new file mode 100644 index 0000000000..dff897b36e --- /dev/null +++ b/comm/mailnews/local/test/unit/head_maillocal.js @@ -0,0 +1,214 @@ +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +var { mailTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/MailTestUtils.jsm" +); +var { localAccountUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/LocalAccountUtils.jsm" +); + +var test = null; + +// WebApps.jsm called by ProxyAutoConfig (PAC) requires a valid nsIXULAppInfo. +var { getAppInfo, newAppInfo, updateAppInfo } = ChromeUtils.importESModule( + "resource://testing-common/AppInfo.sys.mjs" +); +updateAppInfo(); + +// Ensure the profile directory is set up +do_get_profile(); + +var gDEPTH = "../../../../"; + +// Import the pop3 server scripts +/* import-globals-from ../../../test/fakeserver/Maild.jsm */ +/* import-globals-from ../../../test/fakeserver/Auth.jsm */ +/* import-globals-from ../../../test/fakeserver/Pop3d.jsm */ +var { + nsMailServer, + gThreadManager, + fsDebugNone, + fsDebugAll, + fsDebugRecv, + fsDebugRecvSend, +} = ChromeUtils.import("resource://testing-common/mailnews/Maild.jsm"); +var { AuthPLAIN, AuthLOGIN, AuthCRAM } = ChromeUtils.import( + "resource://testing-common/mailnews/Auth.jsm" +); +var { + Pop3Daemon, + POP3_RFC1939_handler, + POP3_RFC2449_handler, + POP3_RFC5034_handler, +} = ChromeUtils.import("resource://testing-common/mailnews/Pop3d.jsm"); + +// Setup the daemon and server +// If the debugOption is set, then it will be applied to the server. +function setupServerDaemon(debugOption) { + var daemon = new Pop3Daemon(); + var extraProps = {}; + function createHandler(d) { + var handler = new POP3_RFC5034_handler(d); + for (var prop in extraProps) { + handler[prop] = extraProps[prop]; + } + return handler; + } + var server = new nsMailServer(createHandler, daemon); + if (debugOption) { + server.setDebugLevel(debugOption); + } + return [daemon, server, extraProps]; +} + +function createPop3ServerAndLocalFolders(port, hostname = "localhost") { + localAccountUtils.loadLocalMailAccount(); + let server = localAccountUtils.create_incoming_server( + "pop3", + port, + "fred", + "wilma", + hostname + ); + return server; +} + +var gCopyListener = { + callbackFunction: null, + copiedMessageHeaderKeys: [], + OnStartCopy() {}, + OnProgress(aProgress, aProgressMax) {}, + SetMessageKey(aKey) { + try { + this.copiedMessageHeaderKeys.push(aKey); + } catch (ex) { + dump(ex); + } + }, + GetMessageId(aMessageId) {}, + OnStopCopy(aStatus) { + if (this.callbackFunction) { + mailTestUtils.do_timeout_function(0, this.callbackFunction, null, [ + this.copiedMessageHeaderKeys, + aStatus, + ]); + } + }, +}; + +/** + * copyFileMessageInLocalFolder + * A utility wrapper of nsIMsgCopyService.copyFileMessage to copy a message + * into local inbox folder. + * + * @param aMessageFile An instance of nsIFile to copy. + * @param aMessageFlags Message flags which will be set after message is + * copied + * @param aMessageKeyword Keywords which will be set for newly copied + * message + * @param aMessageWindow Window for notification callbacks, can be null + * @param aCallback Callback function which will be invoked after + * message is copied + */ +function copyFileMessageInLocalFolder( + aMessageFile, + aMessageFlags, + aMessageKeywords, + aMessageWindow, + aCallback +) { + // Set up local folders + localAccountUtils.loadLocalMailAccount(); + + gCopyListener.callbackFunction = aCallback; + // Copy a message into the local folder + MailServices.copy.copyFileMessage( + aMessageFile, + localAccountUtils.inboxFolder, + null, + false, + aMessageFlags, + aMessageKeywords, + gCopyListener, + aMessageWindow + ); +} + +function do_check_transaction(real, expected) { + // If we don't spin the event loop before starting the next test, the readers + // aren't expired. In this case, the "real" real transaction is the last one. + if (Array.isArray(real)) { + real = real[real.length - 1]; + } + + // real.them may have an extra QUIT on the end, where the stream is only + // closed after we have a chance to process it and not them. We therefore + // excise this from the list + if (real.them[real.them.length - 1] == "QUIT") { + real.them.pop(); + } + + if (expected[0] == "AUTH") { + // We don't send initial AUTH command now. + expected = expected.slice(1); + } + + Assert.equal(real.them.join(","), expected.join(",")); + dump("Passed test " + test + "\n"); +} + +function create_temporary_directory() { + let directory = Services.dirsvc.get("TmpD", Ci.nsIFile); + directory.append("mailFolder"); + directory.createUnique(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0700", 8)); + return directory; +} + +function create_sub_folders(parent, subFolders) { + parent.leafName = parent.leafName + ".sbd"; + parent.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0700", 8)); + + for (let folder in subFolders) { + let subFolder = parent.clone(); + subFolder.append(subFolders[folder].name); + subFolder.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0600", 8)); + if (subFolders[folder].subFolders) { + create_sub_folders(subFolder, subFolders[folder].subFolders); + } + } +} + +function create_mail_directory(subFolders) { + let root = create_temporary_directory(); + + for (let folder in subFolders) { + if (!subFolders[folder].subFolders) { + continue; + } + let directory = root.clone(); + directory.append(subFolders[folder].name); + create_sub_folders(directory, subFolders[folder].subFolders); + } + + return root; +} + +function setup_mailbox(type, mailboxPath) { + let user = Services.uuid.generateUUID().toString(); + let incomingServer = MailServices.accounts.createIncomingServer( + user, + "Local Folder", + type + ); + incomingServer.localPath = mailboxPath; + + return incomingServer.rootFolder; +} + +registerCleanupFunction(function () { + load(gDEPTH + "mailnews/resources/mailShutdown.js"); +}); diff --git a/comm/mailnews/local/test/unit/test_Pop3Channel.js b/comm/mailnews/local/test/unit/test_Pop3Channel.js new file mode 100644 index 0000000000..b518e5b19f --- /dev/null +++ b/comm/mailnews/local/test/unit/test_Pop3Channel.js @@ -0,0 +1,75 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +let [daemon, server] = setupServerDaemon(); +server.start(); +registerCleanupFunction(() => { + server.stop(); +}); + +/** + * Test Pop3Channel can download a partial message correctly. + */ +add_task(async function test_fetchPartialMessage() { + // Set up a test message. + daemon.setMessages(["message1.eml"]); + + // Set up the incoming server to fetch headers only. + let incomingServer = createPop3ServerAndLocalFolders(server.port); + incomingServer + .QueryInterface(Ci.nsILocalMailIncomingServer) + .createDefaultMailboxes(); + incomingServer.headersOnly = true; + + // Use GetNewMail to fetch the headers. + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.pop3.GetNewMail( + null, + urlListener, + incomingServer.rootFolder.getChildNamed("Inbox"), + incomingServer + ); + await urlListener.promise; + + // Check TOP is correctly sent. + let transaction = server.playTransaction(); + do_check_transaction(transaction, [ + "CAPA", + "AUTH PLAIN", + "STAT", + "LIST", + "UIDL", + "TOP 1 0", + ]); + + let streamListener = new PromiseTestUtils.PromiseStreamListener(); + // A nsIPop3URL instance is needed to construct a Pop3Channel, but we can't + // create a nsIPop3URL instance in JS directly. A workaround is constructing a + // mailbox: url with a uidl query, then newChannel will return a Pop3Channel. + let channel = NetUtil.newChannel({ + uri: `${incomingServer.serverURI}/Inbox?uidl=UIDL1`, + loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT, + contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, + }); + channel.asyncOpen(streamListener); + await streamListener.promise; + + // Check RETR is correctly sent. + transaction = server.playTransaction(); + do_check_transaction(transaction, [ + "CAPA", + "AUTH PLAIN", + "STAT", + "LIST", + "UIDL", + "RETR 1", + "DELE 1", + ]); +}); diff --git a/comm/mailnews/local/test/unit/test_bug457168.js b/comm/mailnews/local/test/unit/test_bug457168.js new file mode 100644 index 0000000000..6b2ec41b88 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_bug457168.js @@ -0,0 +1,165 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Protocol tests for POP3. + */ + +var server; +var daemon; +var incomingServer; +var thisTest; + +var tests = [ + { + title: "Get New Mail, One Message", + messages: ["message2.eml", "message2.eml", "message3.eml"], + transaction: [ + "AUTH", + "CAPA", + "AUTH PLAIN", + "STAT", + "LIST", + "UIDL", + "RETR 1", + "DELE 1", + "RETR 2", + "DELE 2", + "RETR 3", + "DELE 3", + ], + }, +]; + +var urlListener = { + OnStartRunningUrl(url) {}, + OnStopRunningUrl(url, result) { + try { + var transaction = server.playTransaction(); + + do_check_transaction(transaction, thisTest.transaction); + + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 2); + Assert.equal(localAccountUtils.inboxFolder.getNumUnread(false), 1); + + Assert.equal(result, 0); + } catch (e) { + // If we have an error, clean up nicely before we throw it. + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_throw(e); + } + + // Let OnStopRunningUrl return cleanly before doing anything else. + do_timeout(0, checkBusy); + }, +}; + +function checkBusy() { + if (tests.length == 0) { + incomingServer.closeCachedConnections(); + + // No more tests, let everything finish + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_test_finished(); + return; + } + + // If the server hasn't quite finished, just delay a little longer. + if (incomingServer.serverBusy) { + do_timeout(20, checkBusy); + return; + } + + testNext(); +} + +function testNext() { + thisTest = tests.shift(); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + server.resetTest(); + + // Set up the test + test = thisTest.title; + + daemon.setMessages(thisTest.messages); + + // Now get the mail + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + + server.performTest(); + } catch (e) { + server.stop(); + + do_throw(e); + } finally { + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +} + +function run_test() { + // Disable new mail notifications + Services.prefs.setBoolPref("mail.biff.play_sound", false); + Services.prefs.setBoolPref("mail.biff.show_alert", false); + Services.prefs.setBoolPref("mail.biff.show_tray_icon", false); + Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false); + Services.prefs.setIntPref("mail.server.default.dup_action", 2); + + server = setupServerDaemon(); + daemon = server[0]; + server = server[1]; + server.start(); + + // Set up the basic accounts and folders + incomingServer = createPop3ServerAndLocalFolders(server.port); + + // Create a cc filter + var filters = incomingServer.getFilterList(null); + + var filter = filters.createFilter("cc dup test"); + filter.filterType = Ci.nsMsgFilterType.Incoming; + var searchTerm = filter.createTerm(); + searchTerm.attrib = Ci.nsMsgSearchAttrib.CC; + searchTerm.op = Ci.nsMsgSearchOp.Contains; + var oldValue = searchTerm.value; + oldValue.attrib = Ci.nsMsgSearchAttrib.CC; + oldValue.str = "dupemail"; + searchTerm.value = oldValue; + filter.appendTerm(searchTerm); + var filterAction = filter.createAction(); + filterAction.type = Ci.nsMsgFilterAction.MarkRead; + filter.appendAction(filterAction); + filter.enabled = true; + + filters.insertFilterAt(0, filter); + + incomingServer.setFilterList(filters); + + // Check that we haven't got any messages in the folder, if we have its a test + // setup issue. + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0); + + do_test_pending(); + + testNext(); +} diff --git a/comm/mailnews/local/test/unit/test_duplicateKey.js b/comm/mailnews/local/test/unit/test_duplicateKey.js new file mode 100644 index 0000000000..845a25cc1a --- /dev/null +++ b/comm/mailnews/local/test/unit/test_duplicateKey.js @@ -0,0 +1,81 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This test deletes intermediate messages, then compacts, then adds more + * messages, testing for duplicated keys in bug 1202105. + */ + +/* import-globals-from ../../../test/resources/POP3pump.js */ +load("../../../resources/POP3pump.js"); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +add_task(async function runPump() { + gPOP3Pump.files = [ + "../../../data/bugmail1", + "../../../data/bugmail1", + "../../../data/bugmail1", + "../../../data/bugmail1", + "../../../data/bugmail1", + ]; + await gPOP3Pump.run(); + + // get message headers for the inbox folder + var hdrs = showMessages(localAccountUtils.inboxFolder); + Assert.equal(hdrs.length, 5, "Check initial db count"); + + // Deletes 2 middle messages. + let deletes = [hdrs[1], hdrs[2]]; + + // Note the listener won't work because this is a sync delete, + // but it should! + localAccountUtils.inboxFolder.deleteMessages( + deletes, + null, // in nsIMsgWindow msgWindow, + true, // in boolean deleteStorage, + true, // in boolean isMove, + null, // in nsIMsgCopyServiceListener, + false + ); // in boolean allowUndo + + dump("Messages after delete\n"); + hdrs = showMessages(localAccountUtils.inboxFolder); + Assert.equal(hdrs.length, 3, "Check db length after deleting two messages"); + + // compact + var listener = new PromiseTestUtils.PromiseUrlListener(); + localAccountUtils.inboxFolder.compact(listener, null); + await listener.promise; + + dump("Messages after compact\n"); + hdrs = showMessages(localAccountUtils.inboxFolder); + Assert.equal(hdrs.length, 3, "Check db length after compact"); + + // Add some more messages. This fails in nsMsgDatabase::AddNewHdrToDB with + // NS_ERROR("adding hdr that already exists") before bug 1202105. + gPOP3Pump.files = ["../../../data/draft1"]; + await gPOP3Pump.run(); + + dump("Messages after new message\n"); + hdrs = showMessages(localAccountUtils.inboxFolder); + Assert.equal(hdrs.length, 4, "Check db length after adding one message"); + + gPOP3Pump = null; +}); + +function showMessages(folder) { + var hdrs = []; + for (let hdr of folder.msgDatabase.enumerateMessages()) { + hdrs.push(hdr); + dump( + "key " + + (hdrs.length - 1) + + " is " + + hdrs[hdrs.length - 1].messageKey + + "\n" + ); + } + return hdrs; +} diff --git a/comm/mailnews/local/test/unit/test_fileName.js b/comm/mailnews/local/test/unit/test_fileName.js new file mode 100644 index 0000000000..e69b1b45a4 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_fileName.js @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Test handling of special chars in folder names + */ + +function run_test() { + let testFolderName = ""; + let OSname = Services.sysinfo.getProperty("name"); + if (OSname == "Windows_NT") { + // On Windows test file with ' ' in the name. + testFolderName = "bugmail 1"; + } else if (OSname == "Linux") { + // On Linux test file with '`' in the name. + testFolderName = "bugmail`1"; + } else if (OSname == "Darwin") { + // On Mac test file with ':' in the name (generated from Mozilla 1.8 branch). + testFolderName = "bugmail:1"; + } else { + // Not sure what this OS is so just use a safe name. + testFolderName = "bugmail1"; + } + + let bugmail = do_get_file("../../../data/bugmail-1"); + let bugmailmsf = do_get_file("../../../data/bugmail-1.msf"); + let localMailDir = do_get_profile().clone(); + localMailDir.append("Mail"); + localMailDir.append("Local Folders"); + let pop3dir = do_get_profile().clone(); + pop3dir.append("Mail"); + pop3dir.append("poptest"); + // Copy the file to the local mail directory + bugmail.copyTo(localMailDir, testFolderName); + bugmailmsf.copyTo(localMailDir, testFolderName + ".msf"); + + // Copy the file to the pop3 server mail directory + bugmail.copyTo(pop3dir, testFolderName); + bugmailmsf.copyTo(pop3dir, testFolderName + ".msf"); + + // These preferences set up a local folders account so we'll use the + // contents of the Local Folders dir we've already pre-populated. + Services.prefs.setCharPref("mail.account.account1.server", "server1"); + Services.prefs.setCharPref("mail.account.account2.server", "server2"); + Services.prefs.setCharPref( + "mail.accountmanager.accounts", + "account1,account2" + ); + Services.prefs.setCharPref( + "mail.accountmanager.localfoldersserver", + "server1" + ); + Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account1"); + Services.prefs.setCharPref( + "mail.server.server1.directory-rel", + "[ProfD]Mail/Local Folders" + ); + Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders"); + Services.prefs.setCharPref("mail.server.server1.name", "Local Folders"); + Services.prefs.setCharPref("mail.server.server1.type", "none"); + Services.prefs.setCharPref("mail.server.server1.userName", "nobody"); + Services.prefs.setCharPref( + "mail.server.server2.directory-rel", + "[ProfD]Mail/poptest" + ); + Services.prefs.setCharPref("mail.server.server2.hostname", "poptest"); + Services.prefs.setCharPref("mail.server.server2.name", "poptest"); + Services.prefs.setCharPref("mail.server.server2.type", "pop3"); + Services.prefs.setCharPref("mail.server.server2.userName", "user"); + // This basically says to ignore the time stamp in the .msf file + Services.prefs.setIntPref("mail.db_timestamp_leeway", 0x7fffffff); + + localAccountUtils.incomingServer = MailServices.accounts.localFoldersServer; + // force load of accounts. + MailServices.accounts.defaultAccount; + + let pop3Server = MailServices.accounts.findServer("user", "poptest", "pop3"); + let rootFolder = + localAccountUtils.incomingServer.rootMsgFolder.QueryInterface( + Ci.nsIMsgLocalMailFolder + ); + let pop3Root = pop3Server.rootMsgFolder; + + // Note: Inbox is not created automatically when there is no deferred server, + // so we need to create it. + localAccountUtils.inboxFolder = rootFolder.createLocalSubfolder("Inbox"); + // a local inbox should have a Mail flag! + localAccountUtils.inboxFolder.setFlag(Ci.nsMsgFolderFlags.Mail); + + rootFolder = localAccountUtils.incomingServer.rootMsgFolder; + bugmail = rootFolder.getChildNamed(testFolderName); + Assert.equal(bugmail.getTotalMessages(false), 1); + bugmail = pop3Root.getChildNamed(testFolderName); + Assert.equal(bugmail.getTotalMessages(false), 1); + + // Check if creating an empty folder returns a proper error + // instead of crash (bug 831190). + try { + rootFolder.createSubfolder("", null); + do_throw("createSubfolder() should have failed on empty folder name."); + } catch (e) { + // NS_MSG_ERROR_INVALID_FOLDER_NAME + Assert.equal(e.result, 2153054242); + } + + // And try to create an existing folder again. + try { + rootFolder.createSubfolder(testFolderName, null); + do_throw("createSubfolder() should have failed on existing folder."); + } catch (e) { + // NS_MSG_FOLDER_EXISTS + Assert.equal(e.result, 2153054227); + } +} diff --git a/comm/mailnews/local/test/unit/test_folderLoaded.js b/comm/mailnews/local/test/unit/test_folderLoaded.js new file mode 100644 index 0000000000..398aa6bff4 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_folderLoaded.js @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * The intent of this file is to show a folder loaded event after a load + * with a null database. + */ + +var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageGenerator.jsm" +); +var { MessageInjection } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageInjection.jsm" +); +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var testSubjects = [ + "[Bug 397009] A filter will let me tag, but not untag", + "Hello, did you receive my bugmail?", +]; +var gMsgFile1 = do_get_file("../../../data/bugmail1"); +var gMsgFile2 = do_get_file("../../../data/draft1"); + +var gTargetFolder = null; + +add_setup(async function () { + if (typeof localAccountUtils.inboxFolder == "undefined") { + localAccountUtils.loadLocalMailAccount(); + } + localAccountUtils.rootFolder.createSubfolder("target", null); + gTargetFolder = localAccountUtils.rootFolder.getChildNamed("target"); + + let copyListenerFile1 = new PromiseTestUtils.PromiseCopyListener(); + MailServices.copy.copyFileMessage( + gMsgFile1, + gTargetFolder, + null, + false, + 0, + "", + copyListenerFile1, + null + ); + await copyListenerFile1.promise; + + let copyListenerFile2 = new PromiseTestUtils.PromiseCopyListener(); + MailServices.copy.copyFileMessage( + gMsgFile2, + gTargetFolder, + null, + false, + 0, + "", + copyListenerFile2, + null + ); + await copyListenerFile2.promise; +}); + +add_task(async function firstUpdate() { + // Get message headers for the target folder. + var msgCount = 0; + for (let hdr of gTargetFolder.msgDatabase.enumerateMessages()) { + msgCount++; + Assert.equal(hdr.subject, testSubjects[msgCount - 1]); + } + Assert.equal(msgCount, 2); + + let folderAddedListener = PromiseTestUtils.promiseFolderEvent( + gTargetFolder, + "FolderLoaded" + ); + gTargetFolder.updateFolder(null); + await folderAddedListener; +}); + +add_task(async function secondUpdate() { + // If the following executes, the test hangs in bug 787557. + gTargetFolder.msgDatabase = null; + let folderAddedListener = PromiseTestUtils.promiseFolderEvent( + gTargetFolder, + "FolderLoaded" + ); + gTargetFolder.updateFolder(null); + await folderAddedListener; +}); diff --git a/comm/mailnews/local/test/unit/test_localFolder.js b/comm/mailnews/local/test/unit/test_localFolder.js new file mode 100644 index 0000000000..de7bb7619f --- /dev/null +++ b/comm/mailnews/local/test/unit/test_localFolder.js @@ -0,0 +1,164 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * nsIMsgFolder.subFolders tests + * These tests intend to test pluggableStore.discoverSubFolders + * and nsIMsgFolder.hasSubFolders. + */ + +// Currently we have two mailbox storage formats. +var gPluggableStores = [ + "@mozilla.org/msgstore/berkeleystore;1", + "@mozilla.org/msgstore/maildirstore;1", +]; + +/** + * Check whether the expected folder structure + * exists in the root folder "mailFolder". + * + * @param expected array of folders and subfolders + * we expect + * @param actual actual subfolders enumerator + */ +function check_sub_folders(expected, actual) { + for (let actualFolder of actual) { + let index; + for (index = 0; index < expected.length; index++) { + if (expected[index].name == actualFolder.name) { + break; + } + } + // If index goes out of bounds, probably we didn't find the name. + Assert.ok(index < expected.length); + + let pluggableStore = actualFolder.msgStore; + pluggableStore.discoverSubFolders(actualFolder, true); + Assert.equal(!!expected[index].subFolders, actualFolder.hasSubFolders); + if (actualFolder.hasSubFolders) { + Assert.equal( + expected[index].subFolders.length, + actualFolder.numSubFolders + ); + check_sub_folders(expected[index].subFolders, actualFolder.subFolders); + } + } +} + +/** + * Test default mailbox without creating any subfolders. + */ +function test_default_mailbox(expected, type) { + let mailbox = setup_mailbox(type, create_temporary_directory()); + + check_sub_folders(expected, mailbox.subFolders); +} + +/** + * A helper method to add the folders in aFolderArray + * to the aParentFolder as subfolders. + * + * @param aFolderArray array of folders and subfolders + * (js objects). + * @param aParentFolder folder (nsIMsgFolder) to which + * the folders and subfolders from + * aFolderArray are to be added. + */ +function add_sub_folders(aFolderArray, aParentFolder) { + for (let msgFolder of aFolderArray) { + if (!aParentFolder.containsChildNamed(msgFolder.name)) { + aParentFolder.createSubfolder(msgFolder.name, null); + } + if (msgFolder.subFolders) { + add_sub_folders( + msgFolder.subFolders, + aParentFolder.getChildNamed(msgFolder.name) + ); + } + } +} + +/** + * Create a server with folders and subfolders from the + * "expected" structure, then create a new server with + * the same filePath, and test that we can discover these + * folders based on that filePath. + */ +function test_mailbox(expected, type) { + let mailboxRootFolder = setup_mailbox(type, create_temporary_directory()); + add_sub_folders(expected, mailboxRootFolder); + + let actualFolder = setup_mailbox(type, mailboxRootFolder.filePath); + check_sub_folders(expected, actualFolder.subFolders); +} + +function run_all_tests() { + test_default_mailbox([{ name: "Trash" }, { name: "Outbox" }], "none"); + test_default_mailbox([{ name: "Inbox" }, { name: "Trash" }], "pop3"); + + // Assuming that the order of the folders returned from the actual folder + // discovery is independent and un-important for this test. + test_mailbox( + [ + { + name: "Inbox", + subFolders: [ + { + name: "sub4", + }, + ], + }, + { + name: "Trash", + }, + ], + "pop3" + ); + + test_mailbox( + [ + { + name: "Inbox", + subFolders: [ + { + name: "inbox-sub1", + subFolders: [ + { + name: "inbox-sub-sub1", + }, + { + name: "inbox-sub-sub2", + }, + ], + }, + { + name: "inbox-sub2", + }, + ], + }, + { + name: "Trash", + }, + { + name: "Outbox", + subFolders: [ + { + name: "outbox-sub1", + }, + ], + }, + ], + "pop3" + ); +} + +function run_test() { + for (let store in gPluggableStores) { + Services.prefs.setCharPref( + "mail.serverDefaultStoreContractID", + gPluggableStores[store] + ); + run_all_tests(); + } +} diff --git a/comm/mailnews/local/test/unit/test_mailboxContentLength.js b/comm/mailnews/local/test/unit/test_mailboxContentLength.js new file mode 100644 index 0000000000..f97a787946 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_mailboxContentLength.js @@ -0,0 +1,62 @@ +/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + * + * ***** END LICENSE BLOCK ***** */ + +/* + * Test content length for the mailbox protocol. This focuses on necko URLs + * that are run externally. + */ + +// Take a multipart message as we're testing attachment URLs as well +var gFile = do_get_file("../../../data/multipart-complex2"); + +function run_test() { + do_test_pending(); + copyFileMessageInLocalFolder(gFile, 0, "", null, verifyContentLength); +} + +function verifyContentLength(aMessageHeaderKeys, aStatus) { + Assert.notEqual(aMessageHeaderKeys, null); + // First get the message URI + let msgHdr = localAccountUtils.inboxFolder.GetMessageHeader( + aMessageHeaderKeys[0] + ); + let messageUri = localAccountUtils.inboxFolder.getUriForMsg(msgHdr); + // Convert this to a URI that necko can run + let messageService = MailServices.messageServiceFromURI(messageUri); + let neckoURL = messageService.getUrlForUri(messageUri); + // Don't use the necko URL directly. Instead, get the spec and create a new + // URL using the IO service + let urlToRun = Services.io.newURI(neckoURL.spec); + + // Get a channel from this URI, and check its content length + let channel = Services.io.newChannelFromURI( + urlToRun, + null, + Services.scriptSecurityManager.getSystemPrincipal(), + null, + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER + ); + Assert.equal(channel.contentLength, gFile.fileSize); + + // Now try an attachment. &part=1.2 + let attachmentURL = Services.io.newURI(neckoURL.spec + "&part=1.2"); + Services.io.newChannelFromURI( + attachmentURL, + null, + Services.scriptSecurityManager.getSystemPrincipal(), + null, + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER + ); + // Currently attachments have their content length set to the length of the + // entire message + Assert.equal(channel.contentLength, gFile.fileSize); + + do_test_finished(); +} diff --git a/comm/mailnews/local/test/unit/test_mailboxProtocol.js b/comm/mailnews/local/test/unit/test_mailboxProtocol.js new file mode 100644 index 0000000000..f7662e8cfb --- /dev/null +++ b/comm/mailnews/local/test/unit/test_mailboxProtocol.js @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for getting mailbox urls via the protocol handler. + */ + +var defaultProtocolFlags = + Ci.nsIProtocolHandler.URI_NORELATIVE | + Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD | + Ci.nsIProtocolHandler.URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT | + Ci.nsIProtocolHandler.URI_FORBIDS_COOKIE_ACCESS | + Ci.nsIProtocolHandler.ORIGIN_IS_FULL_SPEC; + +var protocols = [ + { + protocol: "mailbox", + urlSpec: "mailbox://user@localhost/", + // mailbox protocol doesn't use a port + defaultPort: -1, + }, +]; + +function run_test() { + for (var part = 0; part < protocols.length; ++part) { + print("protocol: " + protocols[part].protocol); + + var pH = Cc[ + "@mozilla.org/network/protocol;1?name=" + protocols[part].protocol + ].createInstance(Ci.nsIProtocolHandler); + + Assert.equal(pH.scheme, protocols[part].protocol); + Assert.equal( + Services.io.getDefaultPort(pH.scheme), + protocols[part].defaultPort + ); + Assert.equal(Services.io.getProtocolFlags(pH.scheme), defaultProtocolFlags); + + // Whip through some of the ports to check we get the right results. + for (let i = 0; i < 1024; ++i) { + Assert.equal(pH.allowPort(i, ""), false); + } + + // Check we get a URI when we ask for one + var uri = Services.io.newURI(protocols[part].urlSpec); + + uri.QueryInterface(Ci.nsIMailboxUrl); + + Assert.equal(uri.spec, protocols[part].urlSpec); + + // XXX This fails on Windows + // do_check_neq(pH.newChannel(uri), null); + } +} diff --git a/comm/mailnews/local/test/unit/test_mailboxURL.js b/comm/mailnews/local/test/unit/test_mailboxURL.js new file mode 100644 index 0000000000..f2330cf8ca --- /dev/null +++ b/comm/mailnews/local/test/unit/test_mailboxURL.js @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Tests for mailbox: URLs. + */ + +var mailboxFile = Services.dirsvc.get("TmpD", Ci.nsIFile); +mailboxFile.append("mailFolder"); +mailboxFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); +var mailboxFileName = Services.io.newFileURI(mailboxFile).pathQueryRef; + +var mailboxURLs = [ + { + url: "mailbox://user@domain@example.com/folder?number=1", + spec: "mailbox://user%40domain@example.com/folder?number=1", + host: "example.com", + port: -1, + scheme: "mailbox", + pathQueryRef: "/folder?number=1", + prePath: "mailbox://user%40domain@example.com", + }, + { + url: "mailbox://nobody@Local%20Folders/folder?number=2", + spec: "mailbox://nobody@local%20folders/folder?number=2", + host: "local%20folders", + port: -1, + scheme: "mailbox", + pathQueryRef: "/folder?number=2", + prePath: "mailbox://nobody@local%20folders", + }, + { + url: "mailbox://" + mailboxFileName + "?number=3", + spec: "mailbox://" + mailboxFileName + "?number=3", + host: "", + port: -1, + scheme: "mailbox", + pathQueryRef: mailboxFileName + "?number=3", + prePath: "mailbox://", + }, +]; + +function run_test() { + registerCleanupFunction(teardown); + var url; + + // Test - get and check urls. + var part = 0; + for (part = 0; part < mailboxURLs.length; part++) { + dump(`url: ${mailboxURLs[part].url}\n`); + url = Services.io.newURI(mailboxURLs[part].url); + + Assert.equal(url.spec, mailboxURLs[part].spec); + Assert.equal(url.scheme, mailboxURLs[part].scheme); + Assert.equal(url.host, mailboxURLs[part].host); + Assert.equal(url.port, mailboxURLs[part].port); + Assert.equal(url.pathQueryRef, mailboxURLs[part].pathQueryRef); + Assert.equal(url.prePath, mailboxURLs[part].prePath); + } + + // Test - Check changing values. + dump("Other Tests\n"); + + // We can set the username on the URLs with a host. + url = Services.io.newURI("mailbox://user@domain@example.com/folder?number=1"); + url.mutate().setUsername("john").finalize(); + url = Services.io.newURI("mailbox://nobody@Local%20Folders/folder?number=2"); + url.mutate().setUsername("jane").finalize(); + + // It should throw on our file-style URLs. + url = Services.io.newURI("mailbox://" + mailboxFileName + "?number=3"); + try { + url.mutate().setUsername("noway").finalize(); + do_throw("Should not be able to set username on file-style mailbox: URL"); + } catch (ex) { + Assert.equal(ex.result, Cr.NS_ERROR_UNEXPECTED); + } +} + +function teardown() { + if (mailboxFile.exists()) { + mailboxFile.remove(false); + } +} diff --git a/comm/mailnews/local/test/unit/test_msgCopy.js b/comm/mailnews/local/test/unit/test_msgCopy.js new file mode 100644 index 0000000000..c49bff465b --- /dev/null +++ b/comm/mailnews/local/test/unit/test_msgCopy.js @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Test of setting keywords with copyFileMessage + +var bugmail11 = do_get_file("../../../data/bugmail11"); + +// main test + +// tag used with test messages +var tag1 = "istag"; + +function run_test() { + do_test_pending(); + copyFileMessageInLocalFolder(bugmail11, 0, tag1, null, test_keywords); +} + +function test_keywords(aMessageHeaderKeys, aStatus) { + let headerKeys = aMessageHeaderKeys; + Assert.notEqual(headerKeys, null); + let copiedMessage = localAccountUtils.inboxFolder.GetMessageHeader( + headerKeys[0] + ); + Assert.equal(copiedMessage.getStringProperty("keywords"), tag1); + do_test_finished(); +} diff --git a/comm/mailnews/local/test/unit/test_msgIDParsing.js b/comm/mailnews/local/test/unit/test_msgIDParsing.js new file mode 100644 index 0000000000..39d435cb1f --- /dev/null +++ b/comm/mailnews/local/test/unit/test_msgIDParsing.js @@ -0,0 +1,24 @@ +/* + * Test bug 676916 - nsParseMailbox parses multi-line message-id header incorrectly + */ + +var headers = + "from: alice@t1.example.com\r\n" + + "to: bob@t2.example.net\r\n" + + "message-id: \r\n <abcmessageid>\r\n"; + +function testMsgID() { + localAccountUtils.inboxFolder.QueryInterface(Ci.nsIMsgLocalMailFolder); + localAccountUtils.inboxFolder.addMessage( + "From \r\n" + headers + "\r\nhello\r\n" + ); + let msgHdr = localAccountUtils.inboxFolder.firstNewMessage; + Assert.equal(msgHdr.messageId, "abcmessageid"); +} + +function run_test() { + for (let storeID of localAccountUtils.pluggableStores) { + localAccountUtils.loadLocalMailAccount(storeID); + testMsgID(); + } +} diff --git a/comm/mailnews/local/test/unit/test_noTop.js b/comm/mailnews/local/test/unit/test_noTop.js new file mode 100644 index 0000000000..a30a03c060 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_noTop.js @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +/** + * A handler with no TOP support. + */ +class NoTopHandler extends POP3_RFC1939_handler { + TOP() { + return this.onError("TOP"); + } +} + +let daemon = new Pop3Daemon(); +let server = new nsMailServer(d => { + let handler = new NoTopHandler(d); + return handler; +}, daemon); +server.start(); +registerCleanupFunction(() => { + server.stop(); +}); + +/** + * Inject a message to the server and do a GetNewMail for the incomingServer. + * + * @param {nsIPop3IncomingServer} incomingServer + */ +async function getNewMail(incomingServer) { + daemon.setMessages(["message1.eml"]); + + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + return urlListener.promise; +} + +/** + * Test TOP is sent even if not advertised, and fallback to RETR after failed. + */ +add_task(async function testNoTop() { + let incomingServer = createPop3ServerAndLocalFolders(server.port); + incomingServer.headersOnly = true; + await getNewMail(incomingServer); + do_check_transaction(server.playTransaction(), [ + "CAPA", + "USER fred", + "PASS wilma", + "STAT", + "LIST", + "UIDL", + "TOP 1 0", + "RETR 1", + "DELE 1", + ]); +}); diff --git a/comm/mailnews/local/test/unit/test_noUidl.js b/comm/mailnews/local/test/unit/test_noUidl.js new file mode 100644 index 0000000000..a3f8de4725 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_noUidl.js @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +/** + * A handler with no UIDL support. + */ +class NoUidlHandler extends POP3_RFC1939_handler { + UIDL() { + return this.onError("UIDL"); + } +} + +let daemon = new Pop3Daemon(); +let server = new nsMailServer(d => { + let handler = new NoUidlHandler(d); + return handler; +}, daemon); +server.start(); +registerCleanupFunction(() => { + server.stop(); +}); + +/** + * Inject a message to the server and do a GetNewMail for the incomingServer. + * + * @param {nsIPop3IncomingServer} incomingServer + */ +async function getNewMail(incomingServer) { + daemon.setMessages(["message1.eml"]); + + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + // Now get the mail. + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + return urlListener.promise; +} + +/** + * Test that RETR and DELE are correctly sent even if UIDL is not supported. + */ +add_task(async function testNoUidl() { + let incomingServer = createPop3ServerAndLocalFolders(server.port); + await getNewMail(incomingServer); + do_check_transaction(server.playTransaction(), [ + "CAPA", + "USER fred", + "PASS wilma", + "STAT", + "LIST", + "UIDL", + "RETR 1", + "DELE 1", + ]); +}); + +/** + * Test that connection is aborted if trying to use headersOnly when UIDL is + * unsupported. + */ +add_task(async function testNoUidlHeadersOnly() { + let incomingServer = createPop3ServerAndLocalFolders(server.port); + incomingServer.headersOnly = true; + await Assert.rejects( + getNewMail(incomingServer), + e => e == Cr.NS_ERROR_FAILURE + ); +}); + +/** + * Test that connection is aborted if trying to use leaveMessagesOnServer when + * UIDL is unsupported. + */ +add_task(async function testNoUidlLeaveMessagesOnServer() { + let incomingServer = createPop3ServerAndLocalFolders(server.port); + incomingServer.leaveMessagesOnServer = true; + await Assert.rejects( + getNewMail(incomingServer), + e => e == Cr.NS_ERROR_FAILURE + ); +}); diff --git a/comm/mailnews/local/test/unit/test_nsIMsgLocalMailFolder.js b/comm/mailnews/local/test/unit/test_nsIMsgLocalMailFolder.js new file mode 100644 index 0000000000..b0b14ecf31 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_nsIMsgLocalMailFolder.js @@ -0,0 +1,321 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Test suite for local folder functions. + */ + +/* import-globals-from ../../../test/resources/MessageGenerator.jsm */ +load("../../../resources/MessageGenerator.jsm"); + +/** + * Bug 66763 + * Test deletion of a folder with a name already existing in Trash. + */ +function subtest_folder_deletion(root) { + // Now we only have <root> and some default subfolders, like Trash. + let trash = root.getChildNamed("Trash"); + Assert.ok(!trash.hasSubFolders); + + // Create new "folder" in root. + let folder = root.createLocalSubfolder("folder"); + let path = folder.filePath; + Assert.ok(path.exists()); + + // Delete "folder" into Trash. + folder.deleteSelf(null); + Assert.ok(!path.exists()); + Assert.equal(trash.numSubFolders, 1); + trash.getChildNamed("folder"); + + // Create another "folder" in root. + folder = root.createLocalSubfolder("folder"); + // Delete "folder" into Trash again. + folder.deleteSelf(null); + Assert.equal(trash.numSubFolders, 2); + // The folder should be automatically renamed as the same name already is in Trash. + trash.getChildNamed("folder(2)"); + + // Create yet another "folder" in root. + folder = root.createLocalSubfolder("folder"); + + // But now with another subfolder + folder + .QueryInterface(Ci.nsIMsgLocalMailFolder) + .createLocalSubfolder("subfolder"); + + // Delete folder into Trash again + folder.deleteSelf(null); + Assert.equal(trash.numSubFolders, 3); + // The folder should be automatically renamed as the same name already is in Trash + // but the subfolder should be untouched. + let folderDeleted3 = trash.getChildNamed("folder(3)"); + Assert.notEqual(folderDeleted3, null); + Assert.ok(folderDeleted3.containsChildNamed("subfolder")); + // Now we have <root> + // +--Trash + // +--folder + // +--folder(2) + // +--folder(3) + // +--subfolder + + // Create another "folder(3)" in root. + Assert.ok(!root.containsChildNamed("folder(3)")); + folder = root.createLocalSubfolder("folder(3)"); + Assert.ok(root.containsChildNamed("folder(3)")); + // Now try to move "folder(3)" from Trash back to root. + // That should fail, because the user gets a prompt about it and that does + // not work in xpcshell. + try { + root.copyFolderLocal(folderDeleted3, true, null, null); + do_throw("copyFolderLocal() should have failed here due to user prompt!"); + } catch (e) { + // Catch only the expected error NS_MSG_ERROR_COPY_FOLDER_ABORTED, + // otherwise fail the test. + if (e.result != 0x8055001a) { + throw e; + } + } +} + +/** + * Test proper creation/rename/removal of folders under a Local mail account + */ +function subtest_folder_operations(root) { + // Test - num/hasSubFolders + + // Get the current number of folders + var numSubFolders = root.numSubFolders; + + var folder = root + .createLocalSubfolder("folder1") + .QueryInterface(Ci.nsIMsgLocalMailFolder); + + Assert.ok(root.hasSubFolders); + Assert.equal(root.numSubFolders, numSubFolders + 1); + + Assert.ok(!folder.hasSubFolders); + Assert.equal(folder.numSubFolders, 0); + + var folder2 = folder.createLocalSubfolder("folder2"); + + Assert.ok(folder.hasSubFolders); + Assert.equal(folder.numSubFolders, 1); + + // Now we have <root> + // +--folder1 + // +--folder2 + + // Test - getChildNamed + + Assert.equal(root.getChildNamed("folder1"), folder); + + // Check for non match, this should throw + var thrown = false; + try { + root.getChildNamed("folder2"); + } catch (e) { + thrown = true; + } + + Assert.ok(thrown); + + // folder2 is a child of folder however. + folder2 = folder.getChildNamed("folder2"); + + // Test - isAncestorOf + + Assert.ok(folder.isAncestorOf(folder2)); + Assert.ok(root.isAncestorOf(folder2)); + Assert.ok(!folder.isAncestorOf(root)); + + // Test - FoldersWithFlag + + folder.setFlag(Ci.nsMsgFolderFlags.CheckNew); + Assert.ok(folder.getFlag(Ci.nsMsgFolderFlags.CheckNew)); + Assert.ok(!folder.getFlag(Ci.nsMsgFolderFlags.Offline)); + + folder.setFlag(Ci.nsMsgFolderFlags.Offline); + Assert.ok(folder.getFlag(Ci.nsMsgFolderFlags.CheckNew)); + Assert.ok(folder.getFlag(Ci.nsMsgFolderFlags.Offline)); + + folder.toggleFlag(Ci.nsMsgFolderFlags.CheckNew); + Assert.ok(!folder.getFlag(Ci.nsMsgFolderFlags.CheckNew)); + Assert.ok(folder.getFlag(Ci.nsMsgFolderFlags.Offline)); + + folder.clearFlag(Ci.nsMsgFolderFlags.Offline); + Assert.ok(!folder.getFlag(Ci.nsMsgFolderFlags.CheckNew)); + Assert.ok(!folder.getFlag(Ci.nsMsgFolderFlags.Offline)); + + folder.setFlag(Ci.nsMsgFolderFlags.Favorite); + folder2.setFlag(Ci.nsMsgFolderFlags.Favorite); + folder.setFlag(Ci.nsMsgFolderFlags.CheckNew); + folder2.setFlag(Ci.nsMsgFolderFlags.Offline); + + Assert.equal(root.getFolderWithFlags(Ci.nsMsgFolderFlags.CheckNew), folder); + + // Test - Move folders around + + var folder3 = root.createLocalSubfolder("folder3"); + var folder3Local = folder3.QueryInterface(Ci.nsIMsgLocalMailFolder); + var folder1Local = folder.QueryInterface(Ci.nsIMsgLocalMailFolder); + + // put a single message in folder1. + let messageGenerator = new MessageGenerator(); + let message = messageGenerator.makeMessage(); + let hdr = folder1Local.addMessage(message.toMboxString()); + Assert.equal(message.messageId, hdr.messageId); + + folder3Local.copyFolderLocal(folder, true, null, null); + + // Test - Get the new folders, make sure the old ones don't exist + + var folder1Moved = folder3.getChildNamed("folder1"); + folder1Moved.getChildNamed("folder2"); + + thrown = false; + try { + root.getChildNamed("folder1"); + } catch (e) { + thrown = true; + } + + Assert.ok(thrown); + + if (folder.filePath.exists()) { + dump("shouldn't exist - folder file path " + folder.URI + "\n"); + } + Assert.ok(!folder.filePath.exists()); + if (folder2.filePath.exists()) { + dump("shouldn't exist - folder2 file path " + folder2.URI + "\n"); + } + Assert.ok(!folder2.filePath.exists()); + + // make sure getting the db doesn't throw an exception + let db = folder1Moved.msgDatabase; + Assert.ok(db.summaryValid); + + // Move folders back, get them + var rootLocal = root.QueryInterface(Ci.nsIMsgLocalMailFolder); + rootLocal.copyFolderLocal(folder1Moved, true, null, null); + folder = root.getChildNamed("folder1"); + folder2 = folder.getChildNamed("folder2"); + + // Test - Rename (test that .msf file is renamed as well) + folder.rename("folder1-newname", null); + // make sure getting the db doesn't throw an exception, and is valid + folder = rootLocal.getChildNamed("folder1-newname"); + db = folder.msgDatabase; + Assert.ok(db.summaryValid); + + folder.rename("folder1", null); + folder = rootLocal.getChildNamed("folder1"); + + // Test - propagateDelete (this tests recursiveDelete as well) + // The folders will be removed from disk completely, not merely to Trash. + + var path1 = folder.filePath; + var path2 = folder2.filePath; + var path3 = folder3.filePath; + + Assert.ok(path1.exists()); + Assert.ok(path2.exists()); + Assert.ok(path3.exists()); + + // First try deleting folder3 -- folder1 and folder2 paths should still exist + root.propagateDelete(folder3, true); + + Assert.ok(path1.exists()); + Assert.ok(path2.exists()); + Assert.ok(!path3.exists()); + + root.propagateDelete(folder, true); + + Assert.ok(!path1.exists()); + Assert.ok(!path2.exists()); +} + +function test_store_rename(root) { + let folder1 = root + .createLocalSubfolder("newfolder1") + .QueryInterface(Ci.nsIMsgLocalMailFolder); + Assert.ok(root.hasSubFolders); + Assert.ok(!folder1.hasSubFolders); + let folder2 = folder1.createLocalSubfolder("newfolder1-sub"); + let folder3 = root + .createLocalSubfolder("newfolder3") + .QueryInterface(Ci.nsIMsgLocalMailFolder); + folder3.createLocalSubfolder("newfolder3-sub"); + + Assert.ok(folder1.hasSubFolders); + Assert.ok(!folder2.hasSubFolders); + Assert.ok(folder3.hasSubFolders); + + folder1.rename("folder1", null); + Assert.ok(root.containsChildNamed("folder1")); + folder1 = root.getChildNamed("folder1"); + + folder1.rename("newfolder1", null); + Assert.ok(root.containsChildNamed("newfolder1")); + folder1 = root.getChildNamed("newfolder1"); + folder2 = folder1.getChildNamed("newfolder1-sub"); + + Assert.ok(folder1.containsChildNamed(folder2.name)); + Assert.ok(folder2.filePath.exists()); + + folder3 = root.getChildNamed("newfolder3"); + root.propagateDelete(folder3, true); + Assert.ok(!root.containsChildNamed("newfolder3")); + folder3 = root + .createLocalSubfolder("newfolder3") + .QueryInterface(Ci.nsIMsgLocalMailFolder); + folder3.createLocalSubfolder("newfolder3-sub"); + folder3.rename("folder3", null); + + Assert.ok(root.containsChildNamed("folder3")); + Assert.ok(!root.containsChildNamed("newfolder3")); +} + +var gPluggableStores = [ + "@mozilla.org/msgstore/berkeleystore;1", + "@mozilla.org/msgstore/maildirstore;1", +]; + +function run_all_tests(aHostName) { + let server = MailServices.accounts.createIncomingServer( + "nobody", + aHostName, + "none" + ); + let account = MailServices.accounts.createAccount(); + account.incomingServer = server; + + let root = server.rootMsgFolder.QueryInterface(Ci.nsIMsgLocalMailFolder); + subtest_folder_operations(root); + subtest_folder_deletion(root); + test_store_rename(root); +} + +function run_test() { + let hostName = "Local Folders"; + let index = 0; + while (index < gPluggableStores.length) { + Services.prefs.setCharPref( + "mail.serverDefaultStoreContractID", + gPluggableStores[index] + ); + run_all_tests(hostName); + hostName += "-" + ++index; + } + + // At this point, + // we should have <root> + // +--newfolder1 + // +--newfolder1-subfolder + // +--newfolder3-anotherName + // +--newfolder3-sub + // +--folder(3) + // +--Trash + // +--folder + // +--folder(2) + // +--folder(3) + // +--subfolder +} diff --git a/comm/mailnews/local/test/unit/test_nsIMsgParseMailMsgState.js b/comm/mailnews/local/test/unit/test_nsIMsgParseMailMsgState.js new file mode 100644 index 0000000000..a64c88f8d6 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_nsIMsgParseMailMsgState.js @@ -0,0 +1,42 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var MSG_LINEBREAK = "\r\n"; + +add_task(async function run_the_test() { + localAccountUtils.loadLocalMailAccount(); + + await test_parse_headers_without_crash("./data/mailformed_recipients.eml"); + await test_parse_headers_without_crash("./data/mailformed_subject.eml"); + await test_parse_headers_without_crash("./data/invalid_mozilla_keys.eml"); +}); + +async function test_parse_headers_without_crash(eml) { + let file = do_get_file(eml); + + let parser = Cc["@mozilla.org/messenger/messagestateparser;1"].createInstance( + Ci.nsIMsgParseMailMsgState + ); + + parser.SetMailDB(localAccountUtils.inboxFolder.getDatabaseWOReparse()); + parser.state = Ci.nsIMsgParseMailMsgState.ParseHeadersState; + + let bytes = await IOUtils.read(file.path); + let mailData = new TextDecoder().decode(bytes); + let lines = mailData.split(MSG_LINEBREAK); + + for (let line = 0; line < lines.length; line++) { + parser.ParseAFolderLine( + lines[line] + MSG_LINEBREAK, + lines[line].length + 2 + ); + } + // Apparently getDatabaseWOReparse doesn't like being called too often + // in a row. + await PromiseTestUtils.promiseDelay(200); +} diff --git a/comm/mailnews/local/test/unit/test_nsIMsgPluggableStore.js b/comm/mailnews/local/test/unit/test_nsIMsgPluggableStore.js new file mode 100644 index 0000000000..c9e7d63864 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_nsIMsgPluggableStore.js @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * nsIMsgPluggableStore interface tests + */ + +function test_discoverSubFolders() { + let mailbox = setup_mailbox("none", create_temporary_directory()); + mailbox.msgStore.discoverSubFolders(mailbox, true); +} + +function test_sliceStream() { + let mailbox = setup_mailbox("none", create_temporary_directory()); + + let str = "Just a test string."; + let strStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + strStream.setData(str, str.length); + + let sliced = mailbox.msgStore.sliceStream(strStream, 7, 4); + + let s = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + s.init(sliced); + + let chunk = s.read(1024); + Assert.equal(chunk, "test", "Check we got the expected subset."); + Assert.equal(s.available(), 0, "Check no more bytes available."); + Assert.equal(s.read(1024), "", "Check read() returns EOF."); +} + +// Return a wrapper which sets the store type before running fn(). +function withStore(store, fn) { + return () => { + Services.prefs.setCharPref("mail.serverDefaultStoreContractID", store); + fn(); + }; +} + +const pluggableStores = [ + "@mozilla.org/msgstore/berkeleystore;1", + "@mozilla.org/msgstore/maildirstore;1", +]; + +for (let store of pluggableStores) { + add_task(withStore(store, test_discoverSubFolders)); + add_task(withStore(store, test_sliceStream)); +} diff --git a/comm/mailnews/local/test/unit/test_over2GBMailboxes.js b/comm/mailnews/local/test/unit/test_over2GBMailboxes.js new file mode 100644 index 0000000000..fe7976b0fe --- /dev/null +++ b/comm/mailnews/local/test/unit/test_over2GBMailboxes.js @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* Test of accessing over 2 GiB local folder. */ + +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +// Find hdr for message whose offset is over 2 GiB. +function findHugeMessageHdr(folder) { + //getMessageHdr() { + for (let header of folder.msgDatabase.enumerateMessages()) { + if (header.messageOffset >= 0x80000000) { + return header; + } + } + + do_throw("Over 2 GiB msgkey was not found!"); + return null; // This won't ever happen, but we're keeping the linter happy. +} + +let gInboxFile; +let gInbox; +let gSmallMsgFile = do_get_file("../../../data/bugmail10"); + +add_setup(async function () { + // Make sure we're using mbox. + Services.prefs.setCharPref( + "mail.serverDefaultStoreContractID", + "@mozilla.org/msgstore/berkeleystore;1" + ); + + localAccountUtils.loadLocalMailAccount(); + + gInbox = localAccountUtils.inboxFolder; + gInboxFile = gInbox.filePath; + + let neededFreeSpace = 0x100000000; + let freeDiskSpace = gInboxFile.diskSpaceAvailable; + info("Free disk space = " + mailTestUtils.toMiBString(freeDiskSpace)); + if (freeDiskSpace < neededFreeSpace) { + throw new Error( + "This test needs " + + mailTestUtils.toMiBString(neededFreeSpace) + + " free space to run. Aborting." + ); + } +}); + +// Extend mbox file to over 2 GiB. +add_task(async function extendPast2GiB() { + let outputStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream) + .QueryInterface(Ci.nsISeekableStream); + // Open in write-only mode, no truncate. + outputStream.init(gInboxFile, 0x02, -1, 0); + // seek past 2GB. + outputStream.seek(0, 0x80000010); + // Write a "space" character. + outputStream.write(" ", 1); + outputStream.close(); +}); + +// Copy another (normal sized) message into the local folder. +// This message should be past the 2GiB position. +add_task(async function appendSmallMessage() { + // Remember initial mbox file size. + let initialInboxSize = gInbox.filePath.fileSize; + info(`Local inbox size (before copyFileMessage()) = ${initialInboxSize}`); + + let copyListener = new PromiseTestUtils.PromiseCopyListener(); + MailServices.copy.copyFileMessage( + gSmallMsgFile, + gInbox, + null /* msgToReplace*/, + false /* isDraftOrTemplate */, + 0 /* message flags */, + "" /* keywords */, + copyListener, + null /* window */ + ); + await copyListener.promise; + + // Make sure inbox file grew (i.e., we were not writing over data). + let localInboxSize = gInbox.filePath.fileSize; + info( + "Local inbox size (after copyFileMessageInLocalFolder()) = " + + localInboxSize + ); + Assert.greater(localInboxSize, initialInboxSize); +}); + +// Copy the huge message into a subfolder. +add_task(async function copyHugeMessage() { + let trash = + localAccountUtils.incomingServer.rootMsgFolder.getChildNamed("Trash"); + let copyListener = new PromiseTestUtils.PromiseCopyListener(); + MailServices.copy.copyMessages( + gInbox, + [findHugeMessageHdr(gInbox)], + trash /* destFolder */, + false, + copyListener, + null, + false + ); + await copyListener.promise; +}); + +// Read out the smaller message beyond the 2 GiB offset and make sure +// it matches what we expect. +add_task(async function verifySmallMessage() { + let msghdr = findHugeMessageHdr(gInbox); + let msgURI = msghdr.folder.getUriForMsg(msghdr); + let msgServ = MailServices.messageServiceFromURI(msgURI); + + let streamListener = new PromiseTestUtils.PromiseStreamListener(); + msgServ.streamMessage(msgURI, streamListener, null, null, false, "", true); + let got = await streamListener.promise; + + let expected = await IOUtils.readUTF8(gSmallMsgFile.path); + Assert.equal(got, expected); +}); + +add_task(async function cleanup() { + // Free up disk space - if you want to look at the file after running + // this test, comment out this line. + gInbox.filePath.remove(false); +}); diff --git a/comm/mailnews/local/test/unit/test_over4GBMailboxes.js b/comm/mailnews/local/test/unit/test_over4GBMailboxes.js new file mode 100644 index 0000000000..befd72fca9 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_over4GBMailboxes.js @@ -0,0 +1,640 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Test to ensure that operations around the 4GiB folder size boundary work correctly. + * This test only works for mbox format mail folders. + * Some of the tests will be removed when support for over 4GiB folders is enabled by default. + * The test functions are executed in this order: + * - run_test + * - ParseListener_run_test + * - downloadUnder4GiB + * - downloadOver4GiB_fail + * - downloadOver4GiB_success + * - downloadOver4GiB_success_check + * - copyIntoOver4GiB_fail + * - copyIntoOver4GiB_fail_check + * - copyIntoOver4GiB_success + * - copyIntoOver4GiB_success_check1 + * - copyIntoOver4GiB_success_check2 + * - compactOver4GiB + * - CompactListener_compactOver4GiB + * - compactUnder4GiB + * - CompactListener_compactUnder4GiB + */ + +// Need to do this before loading POP3Pump.js +Services.prefs.setCharPref( + "mail.serverDefaultStoreContractID", + "@mozilla.org/msgstore/berkeleystore;1" +); + +/* import-globals-from ../../../test/resources/alertTestUtils.js */ +/* import-globals-from ../../../test/resources/POP3pump.js */ +load("../../../resources/alertTestUtils.js"); +load("../../../resources/POP3pump.js"); + +var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageGenerator.jsm" +); +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +// If we're running out of memory parsing the folder, lowering the +// block size might help, though it will slow the test down and consume +// more disk space. +var kSparseBlockSize = 102400000; +var kSizeLimit = 0x100000000; // 4GiB +var kNearLimit = kSizeLimit - 0x1000000; // -16MiB + +var gInboxFile = null; // The mbox file storing the Inbox folder. +var gInboxSize = 0; // The size of the Inbox folder. +var gInbox; // The nsIMsgFolder object of the Inbox folder in Local Folders. +var gExpectedNewMessages = 0; // The number of messages pushed manually into the mbox file. + +var alertIsPending = true; +var alertResolve; +var alertPromise = new Promise(resolve => { + alertResolve = resolve; +}).finally(() => { + alertIsPending = false; +}); +function resetAlertPromise() { + alertIsPending = true; + alertPromise = new Promise(resolve => { + alertResolve = resolve; + }).finally(() => { + alertIsPending = false; + }); +} + +add_setup(async function () { + registerAlertTestUtils(); + + localAccountUtils.loadLocalMailAccount(); + + allow4GBFolders(false); + + gInbox = localAccountUtils.inboxFolder; + gInboxFile = gInbox.filePath; + + let neededFreeSpace = kSizeLimit + 0x10000000; // +256MiB + // On Windows, check whether the drive is NTFS. If it is, mark the file as + // sparse. If it isn't, then bail out now, because in all probability it is + // FAT32, which doesn't support file sizes greater than 4 GiB. + if ( + "@mozilla.org/windows-registry-key;1" in Cc && + mailTestUtils.get_file_system(gInboxFile) != "NTFS" + ) { + throw new Error("On Windows, this test only works on NTFS volumes.\n"); + } + + let freeDiskSpace = gInboxFile.diskSpaceAvailable; + info("Free disk space = " + mailTestUtils.toMiBString(freeDiskSpace)); + if (freeDiskSpace < neededFreeSpace) { + throw new Error( + "This test needs " + + mailTestUtils.toMiBString(neededFreeSpace) + + " free space to run. Aborting." + ); + } + + MailServices.mailSession.AddFolderListener( + FListener, + Ci.nsIFolderListener.all + ); + + // Grow inbox to a size near the max limit. + gExpectedNewMessages = growInbox(kNearLimit); + + // Force the db closed, so that getDatabaseWithReparse will notice + // that it's out of date. + gInbox.msgDatabase.forceClosed(); + gInbox.msgDatabase = null; + let parseUrlListener = new PromiseTestUtils.PromiseUrlListener(); + try { + gInbox.getDatabaseWithReparse(parseUrlListener, gDummyMsgWindow); + } catch (ex) { + Assert.equal(ex.result, Cr.NS_ERROR_NOT_INITIALIZED); + } + await parseUrlListener.promise; + // Check: reparse successful. + Assert.notEqual(gInbox.msgDatabase, null); + Assert.ok(gInbox.msgDatabase.summaryValid); + // Bug 813459 + // Check if the onFolderIntPropertyChanged folder listener hook can return + // values below 2^32 for properties which are not 64 bits long. + Assert.equal(FListener.msgsHistory(0), gExpectedNewMessages); + Assert.equal(FListener.msgsHistory(0), gInbox.getTotalMessages(false)); + Assert.equal(FListener.sizeHistory(0), gInbox.sizeOnDisk); +}); + +/** + * Check we can download new mail when we are near 4GiB limit but do not cross it. + */ +add_task(async function downloadUnder4GiB() { + // Check fake POP3 server is ready. + Assert.notEqual(gPOP3Pump.fakeServer, null); + + // Download a file that still fits into the limit. + let bigFile = do_get_file("../../../data/mime-torture"); + Assert.ok(bigFile.fileSize >= 1024 * 1024); + Assert.ok(bigFile.fileSize <= 1024 * 1024 * 2); + + gPOP3Pump.files = ["../../../data/mime-torture"]; + let pop3Resolve; + let pop3OnDonePromise = new Promise(resolve => { + pop3Resolve = resolve; + }); + gPOP3Pump.onDone = pop3Resolve; + // It must succeed. + gPOP3Pump.run(Cr.NS_OK); + await pop3OnDonePromise; +}); + +/** + * Bug 640371 + * Check we will not cross the 4GiB limit when downloading new mail. + */ +add_task(async function downloadOver4GiB_fail() { + let localInboxSize = gInboxFile.clone().fileSize; + Assert.ok(localInboxSize >= kNearLimit); + Assert.ok(localInboxSize < kSizeLimit); + Assert.equal(gInbox.sizeOnDisk, localInboxSize); + Assert.ok(gInbox.msgDatabase.summaryValid); + // The big file is between 1 and 2 MiB. Append it 16 times to attempt to cross the 4GiB limit. + gPOP3Pump.files = [ + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + ]; + let pop3Resolve; + let pop3OnDonePromise = new Promise(resolve => { + pop3Resolve = resolve; + }); + gPOP3Pump.onDone = pop3Resolve; + // The download must fail. + gPOP3Pump.run(Cr.NS_ERROR_FAILURE); + await pop3OnDonePromise; +}); + +/** + * Bug 789679 + * Check we can cross the 4GiB limit when downloading new mail. + */ +add_task(async function downloadOver4GiB_success_check() { + allow4GBFolders(true); + // Grow inbox to size greater than the max limit (+16 MiB). + gExpectedNewMessages = 16; + // We are in the .onDone() callback of the previous run of gPOP3Pump + // so we need a new POP3Pump so that internal variables of the previous + // one don't get confused. + gPOP3Pump = new POP3Pump(); + gPOP3Pump._incomingServer = gPOP3Pump._createPop3ServerAndLocalFolders(); + gPOP3Pump.files = [ + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + "../../../data/mime-torture", + ]; + let pop3Resolve; + let pop3OnDonePromise = new Promise(resolve => { + pop3Resolve = resolve; + }); + gPOP3Pump.onDone = pop3Resolve; + // The download must not fail. + gPOP3Pump.run(Cr.NS_OK); + await pop3OnDonePromise; + + /** + * Bug 608449 + * Check we can parse a folder if it is above 4GiB. + */ + let localInboxSize = gInboxFile.clone().fileSize; + info( + "Local inbox size (after downloadOver4GiB_success) = " + + localInboxSize + + "\n" + ); + Assert.ok(localInboxSize > kSizeLimit); + Assert.ok(gInbox.msgDatabase.summaryValid); + + // Bug 789679 + // Check if the public SizeOnDisk method can return sizes above 4GB. + Assert.equal(gInbox.sizeOnDisk, localInboxSize); + + // Bug 813459 + // Check if the onFolderIntPropertyChanged folder listener hook can return + // values above 2^32 for properties where it is relevant. + Assert.equal(FListener.sizeHistory(0), gInbox.sizeOnDisk); + Assert.ok(FListener.sizeHistory(1) < FListener.sizeHistory(0)); + Assert.equal( + FListener.msgsHistory(0), + FListener.msgsHistory(16) + gExpectedNewMessages + ); + Assert.equal(gInbox.expungedBytes, 0); + + // Bug 1183490 + // Check that the message keys are below 4GB (thus no offset), + // actually just incrementing by 1 for each message. + let key = 0; + for (let hdr of gInbox.messages) { + key++; + Assert.equal(hdr.messageKey, key); + } +}); + +/** + * Bug 598104 + * Check that copy operation does not allow to grow a local folder above 4 GiB. + */ +add_task(async function copyIntoOver4GiB_fail_check() { + allow4GBFolders(false); + // Save initial file size. + let localInboxSize = gInboxFile.clone().fileSize; + info("Local inbox size (before copyFileMessage) = " + localInboxSize); + + // Use copyFileMessage to (try to) append another message + // to local inbox. + let file = do_get_file("../../../data/mime-torture"); + + // Set up local folders + localAccountUtils.loadLocalMailAccount(); + + let copiedMessageHeaderKeys = []; // Accumulated MsgHdrKeys for listener. + let copyListener = new PromiseTestUtils.PromiseCopyListener({ + SetMessageKey(aKey) { + copiedMessageHeaderKeys.push(aKey); + }, + }); + // Copy a message into the local folder + MailServices.copy.copyFileMessage( + file, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + copyListener, + gDummyMsgWindow + ); + await Assert.rejects( + copyListener.promise, + reason => { + return reason === Cr.NS_ERROR_FAILURE; + }, + "The local folder is not above 4GiB" + ); + + Assert.equal(copiedMessageHeaderKeys.length, 0); + let alertText = await alertPromise; + Assert.ok( + alertText.startsWith( + "The folder Inbox on Local Folders is full, and can't hold any more messages." + ) + ); + + // Make sure inbox file did not grow (i.e., no data were appended). + let newLocalInboxSize = gInboxFile.clone().fileSize; + info("Local inbox size (after copyFileMessage()) = " + newLocalInboxSize); +}); + +/** + * Bug 789679 + * Check that copy operation does allow to grow a local folder above 4 GiB. + */ +add_task(async function copyIntoOver4GiB_success_check1() { + allow4GBFolders(true); + // Append 2 new 2MB messages to the folder. + gExpectedNewMessages = 2; + + // Reset the Promise for alertTestUtils.js. + // This message will be preserved in CompactUnder4GB. + resetAlertPromise(); + let file = do_get_file("../../../data/mime-torture"); + let copiedMessageHeaderKeys = []; // Accumulated MsgHdrKeys for listener. + let copyListener = new PromiseTestUtils.PromiseCopyListener({ + SetMessageKey(aKey) { + copiedMessageHeaderKeys.push(aKey); + }, + }); + // Copy a message into the local folder + MailServices.copy.copyFileMessage( + file, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + copyListener, + gDummyMsgWindow + ); + + await copyListener.promise; + Assert.equal(copiedMessageHeaderKeys[0], 60); + // An alert shouldn't be triggered after our reset. + Assert.ok(alertIsPending); +}); + +add_task(async function copyIntoOver4GiB_success_check2() { + // This message will be removed in compactOver4GB. + let file = do_get_file("../../../data/mime-torture"); + let copiedMessageHeaderKeys = []; // Accumulated MsgHdrKeys for listener. + let copyListener = new PromiseTestUtils.PromiseCopyListener({ + SetMessageKey(aKey) { + copiedMessageHeaderKeys.push(aKey); + }, + }); + // Copy a message into the local folder. + MailServices.copy.copyFileMessage( + file, + localAccountUtils.inboxFolder, + null, + false, + 0, + "", + copyListener, + gDummyMsgWindow + ); + + await copyListener.promise; + Assert.equal(copiedMessageHeaderKeys[0], 61); + // An alert shouldn't be triggered so far. + Assert.ok(alertIsPending); + + Assert.equal( + FListener.msgsHistory(0), + FListener.msgsHistory(2) + gExpectedNewMessages + ); +}); + +/** + * Bug 794303 + * Check we can compact a folder that stays above 4 GiB after compact. + */ +add_task(async function compactOver4GiB() { + gInboxSize = gInboxFile.clone().fileSize; + Assert.ok(gInboxSize > kSizeLimit); + Assert.equal(gInbox.expungedBytes, 0); + // Delete the last small message at folder end. + let doomed = [...gInbox.messages].slice(-1); + let sizeToExpunge = 0; + for (let header of doomed) { + sizeToExpunge = header.messageSize; + } + let deleteListener = new PromiseTestUtils.PromiseCopyListener(); + gInbox.deleteMessages(doomed, null, true, false, deleteListener, false); + await deleteListener.promise; + Assert.equal(gInbox.expungedBytes, sizeToExpunge); + + /* Unfortunately, the compaction now would kill the sparse markings in the file + * so it will really take 4GiB of space in the filesystem and may be slow. */ + // Note: compact() will also add 'X-Mozilla-Status' and 'X-Mozilla-Status2' + // lines to message(s). + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + gInbox.compact(urlListener, null); + await urlListener.promise; + Assert.ok(gInbox.msgDatabase.summaryValid); + // Check that folder size is still above max limit ... + let localInboxSize = gInbox.filePath.clone().fileSize; + info("Local inbox size (after compact 1) = " + localInboxSize); + Assert.ok(localInboxSize > kSizeLimit); + // ... but it got smaller by removing 1 message. + Assert.ok(gInboxSize > localInboxSize); + Assert.equal(gInbox.sizeOnDisk, localInboxSize); +}); + +/** + * Bug 608449 + * Check we can compact a folder to get it under 4 GiB. + */ +add_task(async function compactUnder4GiB() { + // The folder is still above 4GB. + Assert.ok(gInboxFile.clone().fileSize > kSizeLimit); + let folderSize = gInbox.sizeOnDisk; + let totalMsgs = gInbox.getTotalMessages(false); + // Let's close the database and re-open the folder (hopefully dumping memory caches) + // and re-reading the values from disk (msg database). That is to test if + // the values were properly serialized to the database. + gInbox.ForceDBClosed(); + gInbox.msgDatabase = null; + gInbox.getDatabaseWOReparse(); + + Assert.equal(gInbox.sizeOnDisk, folderSize); + Assert.equal(gInbox.getTotalMessages(false), totalMsgs); + + // Very last header in folder is retained, + // but all other preceding headers are marked as deleted. + let doomed = [...gInbox.messages].slice(0, -1); + let sizeToExpunge = gInbox.expungedBytes; // If compact in compactOver4GB was skipped, this is not 0. + for (let header of doomed) { + sizeToExpunge += header.messageSize; + } + let deleteListener = new PromiseTestUtils.PromiseCopyListener(); + gInbox.deleteMessages(doomed, null, true, false, deleteListener, false); + await deleteListener.promise; + + // Bug 894012: size of messages to expunge is now higher than 4GB. + // Only the small 1MiB message remains. + Assert.equal(gInbox.expungedBytes, sizeToExpunge); + Assert.ok(sizeToExpunge > kSizeLimit); + + // Note: compact() will also add 'X-Mozilla-Status' and 'X-Mozilla-Status2' + // lines to message(s). + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + gInbox.compact(urlListener, null); + await urlListener.promise; + // Check: message successfully copied. + Assert.ok(gInbox.msgDatabase.summaryValid); + + // Check that folder size isn't much bigger than our sparse block size, ... + let localInboxSize = gInbox.filePath.clone().fileSize; + info("Local inbox size (after compact 2) = " + localInboxSize); + Assert.equal(gInbox.sizeOnDisk, localInboxSize); + Assert.ok(localInboxSize < kSparseBlockSize + 1000); + // ... i.e., that we just have one message. + Assert.equal(gInbox.getTotalMessages(false), 1); + Assert.equal(FListener.sizeHistory(0), gInbox.sizeOnDisk); + Assert.equal(FListener.msgsHistory(0), 1); + + // The message has its key preserved in compact. + Assert.equal([...gInbox.messages][0].messageKey, 60); +}); + +add_task(function endTest() { + MailServices.mailSession.RemoveFolderListener(FListener); + // Free up disk space - if you want to look at the file after running + // this test, comment out this line. + gInbox.filePath.remove(false); + Services.prefs.clearUserPref("mailnews.allowMboxOver4GB"); + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } +}); + +// This alert() is triggered when file size becomes close (enough) to or +// exceeds 4 GiB. +// See hardcoded value in nsMsgBrkMBoxStore::HasSpaceAvailable(). +function alertPS(parent, aDialogTitle, aText) { + // See "/*/locales/en-US/chrome/*/messenger.properties > mailboxTooLarge". + alertResolve(aText); +} + +// A stub nsIMsgFolderListener that only listens to changes on Inbox and stores +// the seen values for interesting folder properties so we can later test them. +var FListener = { + folderSize: [-1], // an array of seen values of "FolderSize" + totalMsgs: [-1], // an array of seen values of "TotalMessages" + + // Returns the value that is stored 'aBack' entries from the last one in the history. + sizeHistory(aBack) { + return this.folderSize[this.folderSize.length - 1 - aBack]; + }, + msgsHistory(aBack) { + return this.totalMsgs[this.totalMsgs.length - 1 - aBack]; + }, + + onFolderAdded: function act_add(parentFolder, child) {}, + onMessageAdded: function act_add(parentFolder, msg) {}, + onFolderRemoved: function act_remove(parentFolder, child) {}, + onMessageRemoved: function act_remove(parentFolder, msg) {}, + + onFolderPropertyChanged(aItem, aProperty, aOld, aNew) {}, + onFolderIntPropertyChanged(aItem, aProperty, aOld, aNew) { + if (aItem === gInbox) { + info( + "Property change on folder Inbox:" + + aProperty + + "=" + + aOld + + "->" + + aNew + + "\n" + ); + if (aProperty == "FolderSize") { + this.folderSize.push(aNew); + } else if (aProperty == "TotalMessages") { + this.totalMsgs.push(aNew); + } + } + }, + onFolderBoolPropertyChanged(aItem, aProperty, aOld, aNew) {}, + onFolderUnicharPropertyChanged(aItem, aProperty, aOld, aNew) {}, + onFolderPropertyFlagChanged(aItem, aProperty, aOld, aNew) {}, + onFolderEvent(aFolder, aEvent) {}, +}; + +/** + * Allow folders to grow over 4GB. + */ +function allow4GBFolders(aOn) { + Services.prefs.setBoolPref("mailnews.allowMboxOver4GB", aOn); +} + +/** + * Grow local inbox folder to the wanted size using direct appending + * to the underlying file. The folder is filled with copies of a dummy + * message with kSparseBlockSize bytes in size. + * The file must be reparsed (getDatabaseWithReparse) after it is artificially + * enlarged here. + * The file is marked as sparse in the filesystem so that it does not + * really take 4GiB and working with it is faster. + * + * @returns The number of messages created in the folder file. + */ +function growInbox(aWantedSize) { + let msgsAdded = 0; + // Put a single message in the Inbox. + let messageGenerator = new MessageGenerator(); + let message = messageGenerator.makeMessage(); + + // Refresh 'gInboxFile'. + gInboxFile = gInbox.filePath; + let localSize = 0; + + let mboxString = message.toMboxString(); + let plugStore = gInbox.msgStore; + // Grow local inbox to our wished size that is below the max limit. + do { + let sparseStart = gInboxFile.clone().fileSize + mboxString.length; + let nextOffset = Math.min(sparseStart + kSparseBlockSize, aWantedSize - 2); + if (aWantedSize - (nextOffset + 2) < mboxString.length + 2) { + nextOffset = aWantedSize - 2; + } + + // Get stream to write a new message. + let reusable = {}; + let newMsgHdr = {}; + let outputStream = plugStore + .getNewMsgOutputStream(gInbox, newMsgHdr, reusable) + .QueryInterface(Ci.nsISeekableStream); + // Write message header. + outputStream.write(mboxString, mboxString.length); + outputStream.close(); + + // "Add" a new (empty) sparse block at the end of the file. + if (nextOffset - sparseStart == kSparseBlockSize) { + mailTestUtils.mark_file_region_sparse( + gInboxFile, + sparseStart, + kSparseBlockSize + ); + } + + // Append message terminator. + outputStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream) + .QueryInterface(Ci.nsISeekableStream); + // Open in write-only mode, no truncate. + outputStream.init(gInboxFile, 0x02, 0o600, 0); + + // Skip to the wished end of the message. + outputStream.seek(0, nextOffset); + // Add a CR+LF to terminate the message. + outputStream.write("\r\n", 2); + outputStream.close(); + msgsAdded++; + + // Refresh 'gInboxFile'. + gInboxFile = gInbox.filePath; + localSize = gInboxFile.clone().fileSize; + } while (localSize < aWantedSize); + + Assert.equal(gInboxFile.clone().fileSize, aWantedSize); + info( + "Local inbox size = " + + localSize + + "bytes = " + + mailTestUtils.toMiBString(localSize) + ); + Assert.equal(localSize, aWantedSize); + return msgsAdded; +} diff --git a/comm/mailnews/local/test/unit/test_pop3AuthMethods.js b/comm/mailnews/local/test/unit/test_pop3AuthMethods.js new file mode 100644 index 0000000000..3b45287f23 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3AuthMethods.js @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Login tests for POP3 + * + * Test code <copied from="test_pop3GetNewMail.js"> + */ + +var server; +var handler; +var incomingServer; +var thisTest; + +var tests = [ + { + title: "Cleartext password, with server only supporting USER/PASS", + clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext, + serverAuthMethods: [], + expectSuccess: true, + transaction: ["AUTH", "CAPA", "USER fred", "PASS wilma", "STAT"], + }, + { + // Just to make sure we clear the auth flags and re-issue "AUTH" + title: + "Second time Cleartext password, with server only supporting USER/PASS", + clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext, + serverAuthMethods: [], + expectSuccess: true, + transaction: ["AUTH", "CAPA", "USER fred", "PASS wilma", "STAT"], + }, + { + title: + "Cleartext password, with server supporting AUTH PLAIN, LOGIN and CRAM", + clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext, + serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"], + expectSuccess: true, + transaction: ["AUTH", "CAPA", "AUTH PLAIN", "STAT"], + }, + { + title: "Cleartext password, with server supporting only AUTH LOGIN", + clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext, + serverAuthMethods: ["LOGIN"], + expectSuccess: true, + transaction: ["AUTH", "CAPA", "AUTH LOGIN", "STAT"], + }, + { + title: "Encrypted password, with server supporting PLAIN and CRAM", + clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted, + serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"], + expectSuccess: true, + transaction: ["AUTH", "CAPA", "AUTH CRAM-MD5", "STAT"], + }, + { + title: "Encrypted password, try CRAM even if if not advertised", + clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted, + serverAuthMethods: ["PLAIN", "LOGIN"], + expectSuccess: false, + transaction: ["AUTH", "CAPA", "AUTH CRAM-MD5"], + }, + { + title: "Any secure method, with server supporting AUTH PLAIN and CRAM", + clientAuthMethod: Ci.nsMsgAuthMethod.secure, + serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"], + expectSuccess: true, + transaction: ["AUTH", "CAPA", "AUTH CRAM-MD5", "STAT"], + }, + { + title: + "Any secure method, with server only supporting AUTH PLAIN and LOGIN (must fail)", + clientAuthMethod: Ci.nsMsgAuthMethod.secure, + serverAuthMethods: ["PLAIN"], + expectSuccess: false, + transaction: ["AUTH", "CAPA"], + }, +]; + +var urlListener = { + OnStartRunningUrl(url) {}, + OnStopRunningUrl(url, result) { + try { + if (thisTest.expectSuccess) { + Assert.equal(result, 0); + } else { + Assert.notEqual(result, 0); + } + + var transaction = server.playTransaction(); + do_check_transaction(transaction, thisTest.transaction); + + do_timeout(0, checkBusy); + } catch (e) { + server.stop(); + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_throw(e); + } + }, +}; + +function checkBusy() { + if (tests.length == 0) { + incomingServer.closeCachedConnections(); + + // No more tests, let everything finish + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_test_finished(); + return; + } + + // If the server hasn't quite finished, just delay a little longer. + if (incomingServer.serverBusy) { + do_timeout(20, checkBusy); + return; + } + + testNext(); +} + +function testNext() { + thisTest = tests.shift(); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + server.resetTest(); + + test = thisTest.title; + dump("NEXT test: " + thisTest.title + "\n"); + + handler.kAuthSchemes = thisTest.serverAuthMethods; + + // Mailnews caches server capabilities, so try to reset it + // (alternative would be .pop3CapabilityFlags = 0, but this is safer) + deletePop3Server(); + incomingServer = createPop3Server(); + + let msgServer = incomingServer; + msgServer.QueryInterface(Ci.nsIMsgIncomingServer); + msgServer.authMethod = thisTest.clientAuthMethod; + + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + server.performTest(); + } catch (e) { + server.stop(); + do_throw(e); + } +} + +// <copied from="head_maillocal.js::createPop3ServerAndLocalFolders()"> +function createPop3Server() { + let incoming = MailServices.accounts.createIncomingServer( + "fred", + "localhost", + "pop3" + ); + incoming.port = server.port; + incoming.password = "wilma"; + return incoming; +} +// </copied> + +function deletePop3Server() { + if (!incomingServer) { + return; + } + MailServices.accounts.removeIncomingServer(incomingServer, true); + incomingServer = null; +} + +function run_test() { + // Disable new mail notifications + Services.prefs.setBoolPref("mail.biff.play_sound", false); + Services.prefs.setBoolPref("mail.biff.show_alert", false); + Services.prefs.setBoolPref("mail.biff.show_tray_icon", false); + Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false); + + let ssd = setupServerDaemon(); + server = ssd[1]; + handler = ssd[2]; + server.start(); + + // incomingServer = createPop3ServerAndLocalFolders(); + localAccountUtils.loadLocalMailAccount(); + + do_test_pending(); + + testNext(); +} diff --git a/comm/mailnews/local/test/unit/test_pop3Client.js b/comm/mailnews/local/test/unit/test_pop3Client.js new file mode 100644 index 0000000000..19e71a3e78 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3Client.js @@ -0,0 +1,145 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +let [daemon, server, handler] = setupServerDaemon(); +handler.kCapabilities = ["uidl", "top"]; // CAPA response is case-insensitive. +server.start(); +registerCleanupFunction(() => { + server.stop(); +}); + +/** + * Test when alwaysSTARTTLS is set, but the server doesn't support STARTTLS, + * should abort after CAPA response. + */ +add_task(async function testSTARTTLS() { + server.resetTest(); + + let incomingServer = createPop3ServerAndLocalFolders(server.port); + // Set to always use STARTTLS. + incomingServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS; + + let urlListener = { + OnStartRunningUrl() {}, + OnStopRunningUrl(url, result) { + try { + let transaction = server.playTransaction(); + do_check_transaction(transaction, ["AUTH", "CAPA"]); + Assert.equal(result, Cr.NS_ERROR_FAILURE); + } catch (e) { + } finally { + MailServices.accounts.removeIncomingServer(incomingServer, false); + do_test_finished(); + } + }, + }; + + // Now get the mail. + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + + server.performTest(); + + do_test_pending(); +}); + +/** + * Test that depending on user prefs and message size, TOP or RETR should be used. + * + * @param {nsIMsgIncomingServer} incomingServer - A server instance. + * @param {string[]} transaction - The commands sent to the server. + */ +async function testTopOrRetr(incomingServer, transaction) { + server.resetTest(); + // Any message file larger than 50KB is good for this test. + daemon.setMessages(["mailformed_subject.eml"]); + + let urlListener = { + OnStartRunningUrl() {}, + OnStopRunningUrl(url, result) { + try { + do_check_transaction(server.playTransaction(), transaction); + Assert.equal(result, 0); + } catch (e) { + } finally { + MailServices.accounts.removeIncomingServer(incomingServer, false); + do_test_finished(); + } + }, + }; + + // Now get the mail. + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + + server.performTest(); + + do_test_pending(); +} + +/** + * Turn off server.limitOfflineMessageSize, test RETR is used. + */ +add_task(async function testNoOfflineMessageSizeLimit() { + let incomingServer = createPop3ServerAndLocalFolders(server.port); + incomingServer.limitOfflineMessageSize = false; + incomingServer.maxMessageSize = 1; + + testTopOrRetr(incomingServer, [ + "AUTH", + "CAPA", + "AUTH PLAIN", + "STAT", + "LIST", + "UIDL", + "RETR 1", + "DELE 1", + ]); +}); + +/** + * Turn on server.limitOfflineMessageSize and set maxMessageSize to 1KB, test + * TOP is used. + */ +add_task(async function testMaxMessageSize() { + let incomingServer = createPop3ServerAndLocalFolders(server.port); + incomingServer.limitOfflineMessageSize = true; + incomingServer.maxMessageSize = 1; + + testTopOrRetr(incomingServer, [ + "AUTH", + "CAPA", + "AUTH PLAIN", + "STAT", + "LIST", + "UIDL", + "TOP 1 20", + ]); +}); + +/** + * Turn on server.headersOnly, test TOP is used. + */ +add_task(async function testHeadersOnly() { + let incomingServer = createPop3ServerAndLocalFolders(server.port); + incomingServer.headersOnly = true; + + testTopOrRetr(incomingServer, [ + "AUTH", + "CAPA", + "AUTH PLAIN", + "STAT", + "LIST", + "UIDL", + "TOP 1 0", + ]); +}); diff --git a/comm/mailnews/local/test/unit/test_pop3Download.js b/comm/mailnews/local/test/unit/test_pop3Download.js new file mode 100644 index 0000000000..0268170f30 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3Download.js @@ -0,0 +1,81 @@ +/** + * The intent of this file is to test that pop3 download code message storage + * works correctly. + */ + +/* import-globals-from ../../../test/resources/POP3pump.js */ +load("../../../resources/POP3pump.js"); + +var testSubjects = [ + "[Bug 397009] A filter will let me tag, but not untag", + "Hello, did you receive my bugmail?", + "[Bug 655578] list-id filter broken", +]; + +var gMsgHdrs = []; +var gHdrIndex = 0; +var gFiles = [ + "../../../data/bugmail1", + "../../../data/draft1", + "../../../data/bugmail19", +]; + +// This combination of prefs is required to reproduce bug 713611, which +// is what this test is about. +Services.prefs.setBoolPref("mailnews.downloadToTempFile", false); +Services.prefs.setBoolPref("mail.server.default.leave_on_server", true); + +function run_test() { + // add 3 messages + gPOP3Pump.files = gFiles; + gPOP3Pump.onDone = continueTest; + do_test_pending(); + gPOP3Pump.run(); +} + +function continueTest() { + // get message headers for the inbox folder + var msgCount = 0; + for (let hdr of localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()) { + gMsgHdrs.push(hdr); + Assert.equal(hdr.subject, testSubjects[msgCount++]); + } + Assert.equal(msgCount, 3); + gPOP3Pump = null; + streamNextMessage(); +} + +function streamNextMessage() { + let msghdr = gMsgHdrs[gHdrIndex]; + let msgURI = msghdr.folder.getUriForMsg(msghdr); + let msgServ = MailServices.messageServiceFromURI(msgURI); + msgServ.streamMessage(msgURI, gStreamListener, null, null, false, "", true); +} + +var gStreamListener = { + QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]), + _stream: null, + _data: null, + onStartRequest(aRequest) { + this._stream = null; + this._data = ""; + }, + onStopRequest(aRequest, aStatusCode) { + // check that the streamed message starts with "From " + Assert.ok(this._data.startsWith("From ")); + if (++gHdrIndex == gFiles.length) { + do_test_finished(); + } else { + streamNextMessage(); + } + }, + onDataAvailable(aRequest, aInputStream, aOff, aCount) { + if (this._stream == null) { + this._stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + this._stream.init(aInputStream); + } + this._data += this._stream.read(aCount); + }, +}; diff --git a/comm/mailnews/local/test/unit/test_pop3DownloadTempFileHandling.js b/comm/mailnews/local/test/unit/test_pop3DownloadTempFileHandling.js new file mode 100644 index 0000000000..2524c489df --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3DownloadTempFileHandling.js @@ -0,0 +1,62 @@ +/** + * The intent of this file is to test temp file handling when + * downloading multiple pop3 messages with quarantining turned on. + * + * Original author: David Bienvenu <dbienvenu@mozilla.com> + */ + +/* import-globals-from ../../../test/resources/POP3pump.js */ +load("../../../resources/POP3pump.js"); + +var testSubjects = [ + "[Bug 397009] A filter will let me tag, but not untag", + "Hello, did you receive my bugmail?", +]; +var gExpectedFiles; + +function run_test() { + Services.prefs.setBoolPref("mailnews.downloadToTempFile", true); + gExpectedFiles = createExpectedTemporaryFiles(2); + // add 2 messages + gPOP3Pump.files = ["../../../data/bugmail1", "../../../data/draft1"]; + gPOP3Pump.onDone = continueTest; + do_test_pending(); + gPOP3Pump.run(); +} + +function continueTest() { + dump("temp file path = " + gExpectedFiles[0].path + "\n"); + dump("temp file path = " + gExpectedFiles[1].path + "\n"); + for (let expectedFile of gExpectedFiles) { + Assert.ok(!expectedFile.exists()); + } + + // get message headers for the inbox folder + var msgCount = 0; + for (let hdr of localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()) { + Assert.equal(hdr.subject, testSubjects[msgCount++]); + } + Assert.equal(msgCount, 2); + gPOP3Pump = null; + do_test_finished(); +} + +function createExpectedTemporaryFiles(numFiles) { + function createTemporaryFile() { + let file = Services.dirsvc.get("TmpD", Ci.nsIFile); + file.append("newmsg"); + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + return file; + } + + let expectedFiles = []; + for (let i = 0; i < numFiles; i++) { + expectedFiles.push(createTemporaryFile()); + } + + for (let expectedFile of expectedFiles) { + expectedFile.remove(false); + } + + return expectedFiles; +} diff --git a/comm/mailnews/local/test/unit/test_pop3Duplicates.js b/comm/mailnews/local/test/unit/test_pop3Duplicates.js new file mode 100644 index 0000000000..a863cbb1ed --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3Duplicates.js @@ -0,0 +1,40 @@ +/** + * The intent of this file is to test duplicate handling options + * in the pop3 download code. + */ + +/* import-globals-from ../../../test/resources/POP3pump.js */ +load("../../../resources/POP3pump.js"); + +var testSubjects = [ + "[Bug 397009] A filter will let me tag, but not untag", + "Hello, did you receive my bugmail?", +]; + +function run_test() { + // Set duplicate action to be delete duplicates. + Services.prefs.setIntPref( + "mail.server.default.dup_action", + Ci.nsIMsgIncomingServer.deleteDups + ); + // add 3 messages, 2 of which are duplicates. + gPOP3Pump.files = [ + "../../../data/bugmail1", + "../../../data/draft1", + "../../../data/bugmail1", + ]; + gPOP3Pump.onDone = continueTest; + do_test_pending(); + gPOP3Pump.run(); +} + +function continueTest() { + // get message headers for the inbox folder + var msgCount = 0; + for (let hdr of localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()) { + Assert.equal(hdr.subject, testSubjects[msgCount++]); + } + Assert.equal(msgCount, 2); + gPOP3Pump = null; + do_test_finished(); +} diff --git a/comm/mailnews/local/test/unit/test_pop3FilterActions.js b/comm/mailnews/local/test/unit/test_pop3FilterActions.js new file mode 100644 index 0000000000..4c35e46735 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3FilterActions.js @@ -0,0 +1,143 @@ +/* + * This file tests that a pop3 add tag filter writes the new tag + * into the message keywords header. It also tests marking read, + * and flagging messages. + * + * Original author: David Bienvenu <dbienvenu@mozilla.com> + */ + +/* import-globals-from ../../../test/resources/POP3pump.js */ +load("../../../resources/POP3pump.js"); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var gFiles = ["../../../data/bugmail10", "../../../data/bugmail11"]; + +Services.prefs.setBoolPref("mail.server.default.leave_on_server", true); + +// Currently we have two mailbox storage formats. +var gPluggableStores = [ + "@mozilla.org/msgstore/berkeleystore;1", + "@mozilla.org/msgstore/maildirstore;1", +]; + +// Map subject to previews using subject as the key. +var previews = { + "[Bug 436880] IMAP itemDeleted and itemMoveCopyCompleted notifications quite broken": + "Do not reply to this email. You can add comments to this bug at https://bugzilla.mozilla.org/show_bug.cgi?id=436880 -- Configure bugmail: https://bugzilla.mozilla.org/userprefs.cgi?tab=email ------- You are receiving this mail because: -----", + "Bugzilla: confirm account creation": + "Bugzilla has received a request to create a user account using your email address (example@example.org). To confirm that you want to create an account using that email address, visit the following link: https://bugzilla.mozilla.org/token.cgi?t=xxx", +}; + +var gFilter; // the test filter +var gFilterList; +var gTestArray = [ + function createFilters() { + gFilterList = gPOP3Pump.fakeServer.getFilterList(null); + gFilter = gFilterList.createFilter("AddKeyword"); + let searchTerm = gFilter.createTerm(); + searchTerm.matchAll = true; + gFilter.appendTerm(searchTerm); + let tagAction = gFilter.createAction(); + tagAction.type = Ci.nsMsgFilterAction.AddTag; + tagAction.strValue = "TheTag"; + gFilter.appendAction(tagAction); + tagAction = gFilter.createAction(); + tagAction.type = Ci.nsMsgFilterAction.MarkRead; + gFilter.appendAction(tagAction); + tagAction = gFilter.createAction(); + tagAction.type = Ci.nsMsgFilterAction.MarkFlagged; + gFilter.appendAction(tagAction); + gFilter.enabled = true; + gFilter.filterType = Ci.nsMsgFilterType.InboxRule; + gFilterList.insertFilterAt(0, gFilter); + }, + // just get a message into the local folder + async function getLocalMessages1() { + gPOP3Pump.files = gFiles; + await gPOP3Pump.run(); + }, + async function verifyFolders2() { + Assert.equal(folderCount(localAccountUtils.inboxFolder), 2); + + // invalidate the inbox summary file, to be sure that we wrote the keywords + // into the mailbox. + localAccountUtils.inboxFolder.msgDatabase.summaryValid = false; + localAccountUtils.inboxFolder.msgDatabase = null; + localAccountUtils.inboxFolder.ForceDBClosed(); + let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener(); + try { + localAccountUtils.inboxFolder.getDatabaseWithReparse( + promiseUrlListener, + null + ); + } catch (ex) { + await promiseUrlListener.promise; + Assert.ok(ex.result == Cr.NS_ERROR_NOT_INITIALIZED); + return; + } + + // This statement is never reached. + Assert.ok(false); + }, + function verifyMessages() { + let hdrs = []; + let keys = []; + for (let hdr of localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()) { + keys.push(hdr.messageKey); + hdrs.push(hdr); + } + Assert.ok(!localAccountUtils.inboxFolder.fetchMsgPreviewText(keys, null)); + let preview1 = hdrs[0].getStringProperty("preview"); + let preview2 = hdrs[1].getStringProperty("preview"); + Assert.equal(preview1, previews[hdrs[0].subject]); + Assert.equal(preview2, previews[hdrs[1].subject]); + Assert.equal(hdrs[0].getStringProperty("keywords"), "TheTag"); + Assert.equal(hdrs[1].getStringProperty("keywords"), "TheTag"); + Assert.equal( + hdrs[0].flags, + Ci.nsMsgMessageFlags.Read | Ci.nsMsgMessageFlags.Marked + ); + Assert.equal( + hdrs[1].flags, + Ci.nsMsgMessageFlags.Read | Ci.nsMsgMessageFlags.Marked + ); + }, +]; + +function folderCount(folder) { + return [...folder.msgDatabase.enumerateMessages()].length; +} + +function setup_store(storeID) { + return function _setup_store() { + // Initialize pop3Pump with correct mailbox format. + gPOP3Pump.resetPluggableStore(storeID); + + // Set the default mailbox store. + Services.prefs.setCharPref("mail.serverDefaultStoreContractID", storeID); + + // Make sure we're not quarantining messages + Services.prefs.setBoolPref("mailnews.downloadToTempFile", false); + + if (!localAccountUtils.inboxFolder) { + localAccountUtils.loadLocalMailAccount(); + } + }; +} + +function run_test() { + for (let store of gPluggableStores) { + add_task(setup_store(store)); + gTestArray.forEach(x => add_task(x)); + } + + add_task(exitTest); + run_next_test(); +} + +function exitTest() { + info("Exiting mail tests\n"); + gPOP3Pump = null; +} diff --git a/comm/mailnews/local/test/unit/test_pop3Filters.js b/comm/mailnews/local/test/unit/test_pop3Filters.js new file mode 100644 index 0000000000..53ef182a6d --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3Filters.js @@ -0,0 +1,114 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +let [daemon, server, handler] = setupServerDaemon(); +server.start(); +registerCleanupFunction(() => { + server.stop(); +}); + +let incomingServer = createPop3ServerAndLocalFolders(server.port); + +/** + * Inject a message to the server and do a GetNewMail for the incomingServer. + */ +async function getNewMail() { + daemon.setMessages(["message1.eml", "message3.eml"]); + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + return urlListener.promise; +} + +/** + * Test DeleteFromPop3Server filter should send DELE for matched message. + */ +add_task(async function testDeleteFromPop3Server() { + // Turn on leaveMessagesOnServer, so that DELE would not be sent normally. + incomingServer.leaveMessagesOnServer = true; + + // Create a DeleteFromPop3Server filter. + let filterList = incomingServer.getFilterList(null); + let filter = filterList.createFilter("deleteFromServer"); + + let searchTerm = filter.createTerm(); + searchTerm.attrib = Ci.nsMsgSearchAttrib.Subject; + searchTerm.op = Ci.nsMsgSearchOp.Contains; + let value = searchTerm.value; + value.str = "mail 2"; + searchTerm.value = value; + filter.appendTerm(searchTerm); + + let action = filter.createAction(); + action.type = Ci.nsMsgFilterAction.DeleteFromPop3Server; + filter.appendAction(action); + + filter.enabled = true; + filterList.insertFilterAt(0, filter); + + await getNewMail(); + do_check_transaction(server.playTransaction(), [ + "CAPA", + "AUTH PLAIN", + "STAT", + "LIST", + "UIDL", + "RETR 1", // message1.eml doesn't match the filter, no DELE. + "RETR 2", + "DELE 2", // message3.eml matches the filter, DELE was sent. + ]); + + // MailServices.accounts.removeIncomingServer(incomingServer, false); + filterList.removeFilterAt(0); +}); + +/** + * Test FetchBodyFromPop3Server filter should send RETR for matched message. + */ +add_task(async function testFetchBodyFromPop3Server() { + incomingServer.leaveMessagesOnServer = true; + // Turn on leaveMessagesOnServer, so that RETR would not be sent normally. + incomingServer.headersOnly = true; + + // Create a FetchBodyFromPop3Server filter. + let filterList = incomingServer.getFilterList(null); + let filter = filterList.createFilter("fetchBodyFromServer"); + + let searchTerm = filter.createTerm(); + searchTerm.attrib = Ci.nsMsgSearchAttrib.Subject; + searchTerm.op = Ci.nsMsgSearchOp.Contains; + let value = searchTerm.value; + value.str = "mail 2"; + searchTerm.value = value; + filter.appendTerm(searchTerm); + + let action = filter.createAction(); + action.type = Ci.nsMsgFilterAction.FetchBodyFromPop3Server; + filter.appendAction(action); + + filter.enabled = true; + filterList.insertFilterAt(0, filter); + + await getNewMail(); + do_check_transaction(server.playTransaction(), [ + "CAPA", + "AUTH PLAIN", + "STAT", + "LIST", + "UIDL", + "TOP 1 0", // message1.eml doesn't match the filter, no RETR. + "TOP 2 0", + "RETR 2", // message3.eml matches the filter, RETR was sent. + ]); + + filterList.removeFilterAt(0); +}); diff --git a/comm/mailnews/local/test/unit/test_pop3GSSAPIFail.js b/comm/mailnews/local/test/unit/test_pop3GSSAPIFail.js new file mode 100644 index 0000000000..2ff133a14e --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3GSSAPIFail.js @@ -0,0 +1,222 @@ +/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * A server offers GSSAPI (Kerberos), but auth fails, due to client or server. + * + * This mainly tests whether we use the correct login mode. + * + * Whether it fails due to + * - client not set up + * - client ticket expired / not logged in + * - server not being set up properly + * makes no difference to Thunderbird, as that's all hidden in the gssapi-Library + * from the OS. So, the server here just returning err is a good approximation + * of reality of the above cases. + * + * Actually, we (more precisely the OS GSSAPI lib) fail out of band + * in the Kerberos protocol, before the AUTH GSSAPI command is even issued. + * + * @author Ben Bucksch + */ + +var server; +var daemon; +var authSchemes; +var incomingServer; +var thisTest; + +var tests = [ + { + title: "GSSAPI auth, server with GSSAPI only", + clientAuthMethod: Ci.nsMsgAuthMethod.GSSAPI, + serverAuthMethods: ["GSSAPI"], + expectSuccess: false, + transaction: ["AUTH", "CAPA"], + }, + { + // First GSSAPI step happens and fails out of band, thus no "AUTH GSSAPI" + title: "GSSAPI auth, server with GSSAPI and CRAM-MD5", + clientAuthMethod: Ci.nsMsgAuthMethod.GSSAPI, + serverAuthMethods: ["GSSAPI", "CRAM-MD5"], + expectSuccess: false, + transaction: ["AUTH", "CAPA"], + }, + { + title: "Any secure auth, server with GSSAPI only", + clientAuthMethod: Ci.nsMsgAuthMethod.secure, + serverAuthMethods: ["GSSAPI"], + expectSuccess: false, + transaction: ["AUTH", "CAPA"], + }, + { + title: "Any secure auth, server with GSSAPI and CRAM-MD5", + clientAuthMethod: Ci.nsMsgAuthMethod.secure, + serverAuthMethods: ["GSSAPI", "CRAM-MD5"], + expectSuccess: true, + transaction: ["AUTH", "CAPA", "AUTH CRAM-MD5", "STAT"], + }, + { + title: "Encrypted password, server with GSSAPI and CRAM-MD5", + clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted, + serverAuthMethods: ["GSSAPI", "CRAM-MD5"], + expectSuccess: true, + transaction: ["AUTH", "CAPA", "AUTH CRAM-MD5", "STAT"], + }, +]; + +var urlListener = { + OnStartRunningUrl(url) {}, + OnStopRunningUrl(url, result) { + try { + if (thisTest.expectSuccess) { + Assert.equal(result, 0); + } else { + Assert.notEqual(result, 0); + } + + var transaction = server.playTransaction(); + do_check_transaction(transaction, thisTest.transaction); + + do_timeout(0, checkBusy); + } catch (e) { + server.stop(); + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_throw(e); + } + }, +}; + +function checkBusy() { + if (tests.length == 0) { + incomingServer.closeCachedConnections(); + + // No more tests, let everything finish + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_test_finished(); + return; + } + + // If the server hasn't quite finished, just delay a little longer. + if (incomingServer.serverBusy) { + do_timeout(20, checkBusy); + return; + } + + testNext(); +} + +function testNext() { + thisTest = tests.shift(); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + server.resetTest(); + + test = thisTest.title; + dump("NEXT test is: " + thisTest.title + "\n"); + + authSchemes = thisTest.serverAuthMethods; + + // Mailnews caches server capabilities, so try to reset it + deletePop3Server(); + incomingServer = createPop3Server(); + + let msgServer = incomingServer; + msgServer.QueryInterface(Ci.nsIMsgIncomingServer); + msgServer.authMethod = thisTest.clientAuthMethod; + + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + server.performTest(); + } catch (e) { + server.stop(); + do_throw(e); + } +} + +// <copied from="head_maillocal.js::createPop3ServerAndLocalFolders()"> +function createPop3Server() { + let incoming = MailServices.accounts.createIncomingServer( + "fred", + "localhost", + "pop3" + ); + incoming.port = server.port; + incoming.password = "wilma"; + return incoming; +} +// </copied> + +function deletePop3Server() { + if (!incomingServer) { + return; + } + MailServices.accounts.removeIncomingServer(incomingServer, true); + incomingServer = null; +} + +class GSSAPIFail_handler extends POP3_RFC5034_handler { + _needGSSAPI = false; + // kAuthSchemes will be set by test + + AUTH(restLine) { + var scheme = restLine.split(" ")[0]; + if (scheme == "GSSAPI") { + this._multiline = true; + this._needGSSAPI = true; + return "+"; + } + return super.AUTH(restLine); // call parent + } + onMultiline(line) { + if (this._needGSSAPI) { + this._multiline = false; + this._needGSSAPI = false; + return "-ERR hm.... shall I allow you? hm... NO."; + } + + if (super.onMultiline) { + // Call parent. + return super.onMultiline(line); + } + return undefined; + } +} + +function run_test() { + // Disable new mail notifications + Services.prefs.setBoolPref("mail.biff.play_sound", false); + Services.prefs.setBoolPref("mail.biff.show_alert", false); + Services.prefs.setBoolPref("mail.biff.show_tray_icon", false); + Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false); + + daemon = new Pop3Daemon(); + function createHandler(d) { + var handler = new GSSAPIFail_handler(d); + handler.kAuthSchemes = authSchemes; + return handler; + } + server = new nsMailServer(createHandler, daemon); + server.start(); + + // incomingServer = createPop3ServerAndLocalFolders(); + localAccountUtils.loadLocalMailAccount(); + + do_test_pending(); + + testNext(); +} diff --git a/comm/mailnews/local/test/unit/test_pop3GetNewMail.js b/comm/mailnews/local/test/unit/test_pop3GetNewMail.js new file mode 100644 index 0000000000..f0ecf9eb69 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3GetNewMail.js @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Protocol tests for POP3. + */ +var server; +var daemon; +var incomingServer; +var thisTest; + +var tests = [ + { + title: "Get New Mail, No Messages", + messages: [], + transaction: ["AUTH", "CAPA", "AUTH PLAIN", "STAT"], + }, + { + title: "Get New Mail, No Messages 2", + messages: [], + transaction: ["CAPA", "AUTH PLAIN", "STAT"], + }, + { + title: "Get New Mail, One Message", + messages: ["message1.eml"], + transaction: [ + "CAPA", + "AUTH PLAIN", + "STAT", + "LIST", + "UIDL", + "RETR 1", + "DELE 1", + ], + }, +]; + +var urlListener = { + OnStartRunningUrl(url) {}, + OnStopRunningUrl(url, result) { + try { + var transaction = server.playTransaction(); + + do_check_transaction(transaction, thisTest.transaction); + + Assert.equal( + localAccountUtils.inboxFolder.getTotalMessages(false), + thisTest.messages.length + ); + + Assert.equal(result, 0); + } catch (e) { + // If we have an error, clean up nicely before we throw it. + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_throw(e); + } + + // Let OnStopRunningUrl return cleanly before doing anything else. + do_timeout(0, checkBusy); + }, +}; + +function checkBusy() { + if (tests.length == 0) { + incomingServer.closeCachedConnections(); + + // No more tests, let everything finish + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_test_finished(); + return; + } + + // If the server hasn't quite finished, just delay a little longer. + if (incomingServer.serverBusy) { + do_timeout(20, checkBusy); + return; + } + + testNext(); +} + +function testNext() { + thisTest = tests.shift(); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + server.resetTest(); + + // Set up the test + test = thisTest.title; + daemon.setMessages(thisTest.messages); + + // Now get the mail + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + + server.performTest(); + } catch (e) { + server.stop(); + + do_throw(e); + } finally { + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +} + +function run_test() { + // Disable new mail notifications + Services.prefs.setBoolPref("mail.biff.play_sound", false); + Services.prefs.setBoolPref("mail.biff.show_alert", false); + Services.prefs.setBoolPref("mail.biff.show_tray_icon", false); + Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false); + + server = setupServerDaemon(); + daemon = server[0]; + server = server[1]; + server.start(); + + // Set up the basic accounts and folders + incomingServer = createPop3ServerAndLocalFolders(server.port); + + // Check that we haven't got any messages in the folder, if we have its a test + // setup issue. + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0); + + do_test_pending(); + + testNext(); +} diff --git a/comm/mailnews/local/test/unit/test_pop3MoveFilter.js b/comm/mailnews/local/test/unit/test_pop3MoveFilter.js new file mode 100644 index 0000000000..eec79bb63a --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3MoveFilter.js @@ -0,0 +1,137 @@ +/* + * This file tests that a pop3 move filter doesn't leave the + * original message in the inbox. + * + * Original author: David Bienvenu <dbienvenu@mozilla.com> + */ + +/* import-globals-from ../../../test/resources/POP3pump.js */ +load("../../../resources/POP3pump.js"); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var gFiles = ["../../../data/bugmail10", "../../../data/bugmail11"]; + +// make sure limiting download size doesn't causes issues with move filters. +Services.prefs.setBoolPref( + "mail.server.default.limit_offline_message_size", + true +); +Services.prefs.setBoolPref("mail.server.default.leave_on_server", true); + +// Currently we have two mailbox storage formats. +var gPluggableStores = [ + "@mozilla.org/msgstore/berkeleystore;1", + "@mozilla.org/msgstore/maildirstore;1", +]; + +var previews = { + "[Bug 436880] IMAP itemDeleted and itemMoveCopyCompleted notifications quite broken": + "Do not reply to this email. You can add comments to this bug at https://bugzilla.mozilla.org/show_bug.cgi?id=436880 -- Configure bugmail: https://bugzilla.mozilla.org/userprefs.cgi?tab=email ------- You are receiving this mail because: -----", + "Bugzilla: confirm account creation": + "Bugzilla has received a request to create a user account using your email address (example@example.org). To confirm that you want to create an account using that email address, visit the following link: https://bugzilla.mozilla.org/token.cgi?t=xxx", +}; + +var gMoveFolder; +var gFilter; // the test filter +var gFilterList; +var gTestArray = [ + function createFilters() { + gFilterList = gPOP3Pump.fakeServer.getFilterList(null); + gFilter = gFilterList.createFilter("MoveAll"); + let searchTerm = gFilter.createTerm(); + searchTerm.matchAll = true; + gFilter.appendTerm(searchTerm); + let moveAction = gFilter.createAction(); + moveAction.type = Ci.nsMsgFilterAction.MoveToFolder; + moveAction.targetFolderUri = gMoveFolder.URI; + gFilter.appendAction(moveAction); + gFilter.enabled = true; + gFilter.filterType = Ci.nsMsgFilterType.InboxRule; + gFilterList.insertFilterAt(0, gFilter); + }, + // just get a message into the local folder + async function getLocalMessages1() { + gPOP3Pump.files = gFiles; + await gPOP3Pump.run(); + }, + async function verifyFolders2() { + Assert.equal(folderCount(gMoveFolder), 2); + // the local inbox folder should now be empty, since we moved incoming mail. + Assert.equal(folderCount(localAccountUtils.inboxFolder), 0); + + // invalidate the inbox summary file, to be sure that we really moved + // the mail. + localAccountUtils.inboxFolder.msgDatabase.summaryValid = false; + localAccountUtils.inboxFolder.msgDatabase = null; + localAccountUtils.inboxFolder.ForceDBClosed(); + let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener(); + try { + localAccountUtils.inboxFolder.getDatabaseWithReparse( + promiseUrlListener, + null + ); + } catch (ex) { + await promiseUrlListener.promise; + Assert.ok(ex.result == Cr.NS_ERROR_NOT_INITIALIZED); + return; + } + // This statement isn't reached since the error is thrown. + Assert.ok(false); + }, + function verifyMessages() { + let hdrs = []; + let keys = []; + for (let hdr of gMoveFolder.msgDatabase.enumerateMessages()) { + keys.push(hdr.messageKey); + hdrs.push(hdr); + } + Assert.ok(!gMoveFolder.fetchMsgPreviewText(keys, null)); + Assert.equal( + hdrs[0].getStringProperty("preview"), + previews[hdrs[0].subject] + ); + Assert.equal( + hdrs[1].getStringProperty("preview"), + previews[hdrs[1].subject] + ); + }, +]; + +function folderCount(folder) { + return [...folder.msgDatabase.enumerateMessages()].length; +} + +function setup_store(storeID) { + return function _setup_store() { + // Reset pop3Pump with correct mailbox format. + gPOP3Pump.resetPluggableStore(storeID); + + // Make sure we're not quarantining messages + Services.prefs.setBoolPref("mailnews.downloadToTempFile", false); + + if (!localAccountUtils.inboxFolder) { + localAccountUtils.loadLocalMailAccount(); + } + + gMoveFolder = + localAccountUtils.rootFolder.createLocalSubfolder("MoveFolder"); + }; +} + +function run_test() { + for (let store of gPluggableStores) { + add_task(setup_store(store)); + gTestArray.forEach(x => add_task(x)); + } + + add_task(exitTest); + run_next_test(); +} + +function exitTest() { + // Cleanup and exit the test. + info("Exiting mail tests\n"); + gPOP3Pump = null; +} diff --git a/comm/mailnews/local/test/unit/test_pop3MoveFilter2.js b/comm/mailnews/local/test/unit/test_pop3MoveFilter2.js new file mode 100644 index 0000000000..71a7578310 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3MoveFilter2.js @@ -0,0 +1,108 @@ +/* + * This file tests that a pop3 move filter doesn't reuse msg hdr + * info from previous moves. + * + * Original author: David Bienvenu <dbienvenu@mozilla.com> + */ + +/* import-globals-from ../../../test/resources/POP3pump.js */ +load("../../../resources/POP3pump.js"); +var gFiles = ["../../../data/bugmail10", "../../../data/basic1"]; + +Services.prefs.setBoolPref("mail.server.default.leave_on_server", true); + +// Currently we have two mailbox storage formats. +var gPluggableStores = [ + "@mozilla.org/msgstore/berkeleystore;1", + "@mozilla.org/msgstore/maildirstore;1", +]; +var basic1_preview = "Hello, world!"; +var bugmail10_preview = + "Do not reply to this email. You can add comments to this bug at https://bugzilla.mozilla.org/show_bug.cgi?id=436880 -- Configure bugmail: https://bugzilla.mozilla.org/userprefs.cgi?tab=email ------- You are receiving this mail because: -----"; + +var gMoveFolder; +var gFilter; // the test filter +var gFilterList; +var gTestArray = [ + function createFilters() { + gFilterList = gPOP3Pump.fakeServer.getFilterList(null); + // create a cc filter which will match the first message but not the second. + gFilter = gFilterList.createFilter("MoveCc"); + let searchTerm = gFilter.createTerm(); + searchTerm.attrib = Ci.nsMsgSearchAttrib.CC; + searchTerm.op = Ci.nsMsgSearchOp.Contains; + var oldValue = searchTerm.value; + oldValue.attrib = Ci.nsMsgSearchAttrib.CC; + oldValue.str = "invalid@example.com"; + searchTerm.value = oldValue; + gFilter.appendTerm(searchTerm); + let moveAction = gFilter.createAction(); + moveAction.type = Ci.nsMsgFilterAction.MoveToFolder; + moveAction.targetFolderUri = gMoveFolder.URI; + gFilter.appendAction(moveAction); + gFilter.enabled = true; + gFilter.filterType = Ci.nsMsgFilterType.InboxRule; + gFilterList.insertFilterAt(0, gFilter); + }, + // just get a message into the local folder + async function getLocalMessages1() { + gPOP3Pump.files = gFiles; + await gPOP3Pump.run(); + }, + function verifyFolders2() { + Assert.equal(folderCount(gMoveFolder), 1); + // the local inbox folder should have one message. + Assert.equal(folderCount(localAccountUtils.inboxFolder), 1); + }, + function verifyMessages() { + // check MoveFolder message + let hdr = [...gMoveFolder.msgDatabase.enumerateMessages()][0]; + Assert.ok(!gMoveFolder.fetchMsgPreviewText([hdr.messageKey], null)); + Assert.equal(hdr.getStringProperty("preview"), bugmail10_preview); + // check inbox message + hdr = [...localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()][0]; + Assert.ok( + !localAccountUtils.inboxFolder.fetchMsgPreviewText([hdr.messageKey], null) + ); + Assert.equal(hdr.getStringProperty("preview"), basic1_preview); + }, +]; + +function folderCount(folder) { + return [...folder.msgDatabase.enumerateMessages()].length; +} + +function setup_store(storeID) { + return function _setup_store() { + // Initialize pop3Pump with correct mailbox format. + gPOP3Pump.resetPluggableStore(storeID); + + // Set the default mailbox store. + Services.prefs.setCharPref("mail.serverDefaultStoreContractID", storeID); + + // Make sure we're not quarantining messages + Services.prefs.setBoolPref("mailnews.downloadToTempFile", false); + if (!localAccountUtils.inboxFolder) { + localAccountUtils.loadLocalMailAccount(); + } + + gMoveFolder = + localAccountUtils.rootFolder.createLocalSubfolder("MoveFolder"); + }; +} + +function run_test() { + for (let store of gPluggableStores) { + add_task(setup_store(store)); + gTestArray.forEach(x => add_task(x)); + } + + add_task(exitTest); + run_next_test(); +} + +function exitTest() { + // Cleanup and exit the test. + info("Exiting mail tests\n"); + gPOP3Pump = null; +} diff --git a/comm/mailnews/local/test/unit/test_pop3MultiCopy.js b/comm/mailnews/local/test/unit/test_pop3MultiCopy.js new file mode 100644 index 0000000000..42a0f67e23 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3MultiCopy.js @@ -0,0 +1,97 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This tests that copied multiple messages in maildir are correct. + */ + +/* import-globals-from ../../../test/resources/POP3pump.js */ +load("../../../resources/POP3pump.js"); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var testSubjects = [ + "[Bug 397009] A filter will let me tag, but not untag", + "Hello, did you receive my bugmail?", +]; + +Services.prefs.setCharPref( + "mail.serverDefaultStoreContractID", + "@mozilla.org/msgstore/maildirstore;1" +); + +add_task(async function runPump() { + // Test for multiple message copy for maildir. + let storeID = "@mozilla.org/msgstore/maildirstore;1"; + gPOP3Pump.resetPluggableStore(storeID); + // Set the default mailbox store. + Services.prefs.setCharPref("mail.serverDefaultStoreContractID", storeID); + + // We want to test cross-server copy, so don't defer. + gPOP3Pump.fakeServer.deferredToAccount = ""; + + gPOP3Pump.files = ["../../../data/bugmail1", "../../../data/draft1"]; + await gPOP3Pump.run(); + + // get message headers for the inbox folder + let inbox = gPOP3Pump.fakeServer.rootMsgFolder.getFolderWithFlags( + Ci.nsMsgFolderFlags.Inbox + ); + dump("inbox is at " + inbox.filePath.path + "\n"); + + // Accumulate messages to copy. + let messages = []; + let msgCount = 0; + for (let hdr of inbox.msgDatabase.enumerateMessages()) { + msgCount++; + messages.push(hdr); + Assert.equal(hdr.subject, testSubjects[msgCount - 1]); + } + Assert.equal(messages.length, 2); + + // Create a test folder on the Local Folders account. + let testFolder = localAccountUtils.rootFolder + .QueryInterface(Ci.nsIMsgLocalMailFolder) + .createLocalSubfolder("test"); + dump("testFolder is at " + testFolder.filePath.path + "\n"); + + // Copy messages to that folder. + let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener(); + MailServices.copy.copyMessages( + inbox, + messages, + testFolder, + false, + promiseCopyListener, + null, + false + ); + await promiseCopyListener.promise; + + // Check the destination headers. + messages = []; + msgCount = 0; + let subjects = []; + for (let hdr of testFolder.msgDatabase.enumerateMessages()) { + msgCount++; + messages.push(hdr); + dump("Subject: " + hdr.subject + "\n"); + subjects.push(hdr.subject); + } + Assert.equal(messages.length, 2); + + // Check for subjects. maildir order for messages may not match + // order for creation, hence the array.includes. + for (let subject of testSubjects) { + Assert.ok(subjects.includes(subject)); + } + + // Make sure the body matches the message. + for (let hdr of testFolder.msgDatabase.enumerateMessages()) { + let body = mailTestUtils.loadMessageToString(testFolder, hdr); + Assert.ok(body.includes(hdr.subject)); + } + + gPOP3Pump = null; +}); diff --git a/comm/mailnews/local/test/unit/test_pop3MultiCopy2.js b/comm/mailnews/local/test/unit/test_pop3MultiCopy2.js new file mode 100644 index 0000000000..007a0fd99b --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3MultiCopy2.js @@ -0,0 +1,179 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This tests that moved multiple messages from maildir->mbox and + mbox->maildir are correct. + */ + +/* import-globals-from ../../../test/resources/POP3pump.js */ +load("../../../resources/POP3pump.js"); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +Services.prefs.setCharPref( + "mail.serverDefaultStoreContractID", + "@mozilla.org/msgstore/maildirstore;1" +); + +var gInboxFolder, gTestFolder; + +gPOP3Pump.files = ["../../../data/bugmail1", "../../../data/draft1"]; +var gTestSubjects = [ + "[Bug 397009] A filter will let me tag, but not untag", + "Hello, did you receive my bugmail?", +]; + +add_setup(async function () { + let storeID = "@mozilla.org/msgstore/maildirstore;1"; + resetPluggableStoreLocal(storeID); + + // We want to test cross-server copy, so don't defer. + gPOP3Pump.fakeServer.deferredToAccount = ""; + + // Create a test folder on the Local Folders account. + gTestFolder = localAccountUtils.rootFolder + .QueryInterface(Ci.nsIMsgLocalMailFolder) + .createLocalSubfolder("test"); + dump("testFolder is at " + gTestFolder.filePath.path + "\n"); + await gPOP3Pump.run(); +}); + +add_task(async function maildirToMbox() { + // Test for multiple message copy for maildir->mbox. + + // get message headers for the inbox folder + gInboxFolder = gPOP3Pump.fakeServer.rootMsgFolder.getFolderWithFlags( + Ci.nsMsgFolderFlags.Inbox + ); + dump("inbox is at " + gInboxFolder.filePath.path + "\n"); + + // Accumulate messages to copy. + let messages = []; + for (let hdr of gInboxFolder.msgDatabase.enumerateMessages()) { + messages.push(hdr); + } + Assert.equal(messages.length, 2); + + // Move messages to mbox test folder. + let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener(); + MailServices.copy.copyMessages( + gInboxFolder, + messages, + gTestFolder, + true, // isMove + promiseCopyListener, + null, // window + false // allowUndo + ); + await promiseCopyListener.promise; + + // Check the destination headers. + messages = []; + let subjects = []; + for (let hdr of gTestFolder.msgDatabase.enumerateMessages()) { + messages.push(hdr); + dump("Subject: " + hdr.subject + "\n"); + subjects.push(hdr.subject); + } + Assert.equal(messages.length, 2); + + // messages should be missing from source + Assert.equal(gInboxFolder.getTotalMessages(false), 0); + + // Check for subjects. maildir order for messages may not match + // order for creation, hence the array.includes. + for (let subject of gTestSubjects) { + Assert.ok(subjects.includes(subject)); + } + + // Make sure the body matches the message. + for (let hdr of gTestFolder.msgDatabase.enumerateMessages()) { + let body = mailTestUtils.loadMessageToString(gTestFolder, hdr); + Assert.ok(body.includes(hdr.subject)); + } +}); + +add_task(async function mboxToMaildir() { + // Test for multiple message copy for mbox->maildir. + + // Accumulate messages to copy. + let messages = []; + for (let hdr of gTestFolder.msgDatabase.enumerateMessages()) { + messages.push(hdr); + } + Assert.equal(messages.length, 2); + + // Move messages to inbox folder. + let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener(); + MailServices.copy.copyMessages( + gTestFolder, + messages, + gInboxFolder, + true, + promiseCopyListener, + null, + false + ); + await promiseCopyListener.promise; + + // Check the destination headers. + messages = []; + let subjects = []; + for (let hdr of gInboxFolder.msgDatabase.enumerateMessages()) { + messages.push(hdr); + dump("Subject: " + hdr.subject + "\n"); + subjects.push(hdr.subject); + } + Assert.equal(messages.length, 2); + + // messages should be missing from source + Assert.equal(gTestFolder.getTotalMessages(false), 0); + + // Check for subjects. maildir order for messages may not match + // order for creation, hence the array.includes. + for (let subject of gTestSubjects) { + Assert.ok(subjects.includes(subject)); + } + + // Make sure the body matches the message. + for (let hdr of gInboxFolder.msgDatabase.enumerateMessages()) { + let body = mailTestUtils.loadMessageToString(gInboxFolder, hdr); + Assert.ok(body.includes(hdr.subject)); + } +}); + +add_task(function testCleanup() { + gPOP3Pump = null; +}); + +// Clone of POP3pump resetPluggableStore that does not reset local folders. +function resetPluggableStoreLocal(aStoreContractID) { + Services.prefs.setCharPref( + "mail.serverDefaultStoreContractID", + aStoreContractID + ); + + // Cleanup existing files, server and account instances, if any. + if (gPOP3Pump._server) { + gPOP3Pump._server.stop(); + } + + if (gPOP3Pump.fakeServer && gPOP3Pump.fakeServer.valid) { + gPOP3Pump.fakeServer.closeCachedConnections(); + MailServices.accounts.removeIncomingServer(gPOP3Pump.fakeServer, false); + } + + gPOP3Pump.fakeServer = localAccountUtils.create_incoming_server( + "pop3", + gPOP3Pump.kPOP3_PORT, + "fred", + "wilma" + ); + + // localAccountUtils.clearAll(); + + gPOP3Pump._incomingServer = gPOP3Pump.fakeServer; + gPOP3Pump._mailboxStoreContractID = aStoreContractID; +} diff --git a/comm/mailnews/local/test/unit/test_pop3Password.js b/comm/mailnews/local/test/unit/test_pop3Password.js new file mode 100644 index 0000000000..8cfd0de79d --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3Password.js @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Authentication tests for POP3. + */ + +/* import-globals-from ../../../test/resources/passwordStorage.js */ +load("../../../resources/passwordStorage.js"); + +var server; +var daemon; +var incomingServer; +var thisTest; + +var tests = [ + { + title: "Get New Mail, One Message", + messages: ["message1.eml"], + transaction: [ + "AUTH", + "CAPA", + "AUTH PLAIN", + "STAT", + "LIST", + "UIDL", + "RETR 1", + "DELE 1", + ], + }, +]; + +var urlListener = { + OnStartRunningUrl(url) {}, + OnStopRunningUrl(url, result) { + try { + var transaction = server.playTransaction(); + + do_check_transaction(transaction, thisTest.transaction); + + Assert.equal( + localAccountUtils.inboxFolder.getTotalMessages(false), + thisTest.messages.length + ); + + Assert.equal(result, 0); + } catch (e) { + // If we have an error, clean up nicely before we throw it. + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_throw(e); + } + + // Let OnStopRunningUrl return cleanly before doing anything else. + do_timeout(0, checkBusy); + }, +}; + +function checkBusy() { + if (tests.length == 0) { + incomingServer.closeCachedConnections(); + + // No more tests, let everything finish + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_test_finished(); + return; + } + + // If the server hasn't quite finished, just delay a little longer. + if (incomingServer.serverBusy) { + do_timeout(20, checkBusy); + return; + } + + testNext(); +} + +function testNext() { + thisTest = tests.shift(); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + server.resetTest(); + + // Set up the test + test = thisTest.title; + daemon.setMessages(thisTest.messages); + + // Now get the mail + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + + server.performTest(); + } catch (e) { + server.stop(); + + do_throw(e); + } finally { + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +} + +add_task(async function () { + // Disable new mail notifications + Services.prefs.setBoolPref("mail.biff.play_sound", false); + Services.prefs.setBoolPref("mail.biff.show_alert", false); + Services.prefs.setBoolPref("mail.biff.show_tray_icon", false); + Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false); + + // Prepare files for passwords (generated by a script in bug 1018624). + await setupForPassword("signons-mailnews1.8.json"); + + // Set up the Server + var serverArray = setupServerDaemon(); + daemon = serverArray[0]; + server = serverArray[1]; + var handler = serverArray[2]; + server.start(); + + // Login information needs to match the one stored in the signons json file. + handler.kUsername = "testpop3"; + handler.kPassword = "pop3test"; + + // Set up the basic accounts and folders. + // We would use createPop3ServerAndLocalFolders() however we want to have + // a different username and NO password for this test (as we expect to load + // it from the signons json file in which the login information is stored). + localAccountUtils.loadLocalMailAccount(); + + incomingServer = MailServices.accounts.createIncomingServer( + "testpop3", + "localhost", + "pop3" + ); + + incomingServer.port = server.port; + + // Check that we haven't got any messages in the folder, if we have its a test + // setup issue. + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0); + + do_test_pending(); + + testNext(); +}); diff --git a/comm/mailnews/local/test/unit/test_pop3Password2.js b/comm/mailnews/local/test/unit/test_pop3Password2.js new file mode 100644 index 0000000000..9bc32e471b --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3Password2.js @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Authentication tests for POP3 - checks for servers whose details have + * changed (e.g. realusername and realhostname are different from username and + * hostname). + */ + +/* import-globals-from ../../../test/resources/passwordStorage.js */ +load("../../../resources/passwordStorage.js"); + +var server; +var daemon; +var incomingServer; +var thisTest; + +var tests = [ + { + title: "Get New Mail, One Message", + messages: ["message1.eml"], + transaction: [ + "AUTH", + "CAPA", + "AUTH PLAIN", + "STAT", + "LIST", + "UIDL", + "RETR 1", + "DELE 1", + ], + }, +]; + +var urlListener = { + OnStartRunningUrl(url) {}, + OnStopRunningUrl(url, result) { + try { + var transaction = server.playTransaction(); + + do_check_transaction(transaction, thisTest.transaction); + + Assert.equal( + localAccountUtils.inboxFolder.getTotalMessages(false), + thisTest.messages.length + ); + + Assert.equal(result, 0); + } catch (e) { + // If we have an error, clean up nicely before we throw it. + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_throw(e); + } + + // Let OnStopRunningUrl return cleanly before doing anything else. + do_timeout(0, checkBusy); + }, +}; + +function checkBusy() { + if (tests.length == 0) { + incomingServer.closeCachedConnections(); + + // No more tests, let everything finish + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_test_finished(); + return; + } + + // If the server hasn't quite finished, just delay a little longer. + if (incomingServer.serverBusy) { + do_timeout(20, checkBusy); + return; + } + + testNext(); +} + +function testNext() { + thisTest = tests.shift(); + + // Handle the server in a try/catch/finally loop so that we always will stop + // the server if something fails. + try { + server.resetTest(); + + // Set up the test + test = thisTest.title; + daemon.setMessages(thisTest.messages); + + // Now get the mail + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + + server.performTest(); + } catch (e) { + server.stop(); + + do_throw(e); + } finally { + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +} + +add_task(async function () { + // Disable new mail notifications + Services.prefs.setBoolPref("mail.biff.play_sound", false); + Services.prefs.setBoolPref("mail.biff.show_alert", false); + Services.prefs.setBoolPref("mail.biff.show_tray_icon", false); + Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false); + + // These preferences set up a local pop server that has had its hostname + // and username changed from the original settings. We can't do this by + // function calls for this test as they would cause the password to be + // forgotten when changing the hostname/username and this breaks the test. + Services.prefs.setCharPref("mail.account.account1.server", "server1"); + Services.prefs.setCharPref("mail.account.account2.server", "server2"); + Services.prefs.setCharPref("mail.account.account2.identities", "id1"); + Services.prefs.setCharPref( + "mail.accountmanager.accounts", + "account1,account2" + ); + Services.prefs.setCharPref( + "mail.accountmanager.localfoldersserver", + "server1" + ); + Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account2"); + Services.prefs.setCharPref("mail.identity.id1.fullName", "testpop3"); + Services.prefs.setCharPref( + "mail.identity.id1.useremail", + "testpop3@localhost" + ); + Services.prefs.setBoolPref("mail.identity.id1.valid", true); + Services.prefs.setCharPref( + "mail.server.server1.directory-rel", + "[ProfD]Mail/Local Folders" + ); + Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders"); + Services.prefs.setCharPref("mail.server.server1.name", "Local Folders"); + Services.prefs.setCharPref("mail.server.server1.type", "none"); + Services.prefs.setCharPref("mail.server.server1.userName", "nobody"); + Services.prefs.setCharPref( + "mail.server.server2.directory-rel", + "[ProfD]Mail/invalid" + ); + Services.prefs.setCharPref("mail.server.server2.hostname", "invalid"); + Services.prefs.setCharPref( + "mail.server.server2.name", + "testpop3 on localhost" + ); + Services.prefs.setCharPref("mail.server.server2.realhostname", "localhost"); + Services.prefs.setCharPref("mail.server.server2.realuserName", "testpop3"); + Services.prefs.setCharPref("mail.server.server2.type", "pop3"); + Services.prefs.setCharPref("mail.server.server2.userName", "othername"); + + // Prepare files for passwords (generated by a script in bug 1018624). + await setupForPassword("signons-mailnews1.8-alt.json"); + + // Set up the Server + var serverArray = setupServerDaemon(); + daemon = serverArray[0]; + server = serverArray[1]; + var handler = serverArray[2]; + server.start(); + Services.prefs.setIntPref("mail.server.server2.port", server.port); + + // Login information needs to match the one stored in the signons json file. + handler.kUsername = "testpop3"; + handler.kPassword = "pop3test"; + + MailServices.accounts.loadAccounts(); + + localAccountUtils.incomingServer = MailServices.accounts.localFoldersServer; + + var rootFolder = + localAccountUtils.incomingServer.rootMsgFolder.QueryInterface( + Ci.nsIMsgLocalMailFolder + ); + + // Note: Inbox is not created automatically when there is no deferred server, + // so we need to create it. + localAccountUtils.inboxFolder = rootFolder.createLocalSubfolder("Inbox"); + // a local inbox should have a Mail flag! + localAccountUtils.inboxFolder.setFlag(Ci.nsMsgFolderFlags.Mail); + + // Create the incoming server with "original" details. + incomingServer = MailServices.accounts.getIncomingServer("server2"); + + // Check that we haven't got any messages in the folder, if we have its a test + // setup issue. + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0); + + do_test_pending(); + + testNext(); +}); diff --git a/comm/mailnews/local/test/unit/test_pop3Password3.js b/comm/mailnews/local/test/unit/test_pop3Password3.js new file mode 100644 index 0000000000..b12181e746 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3Password3.js @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Extra tests for POP3 passwords (forgetPassword) + */ + +/* import-globals-from ../../../test/resources/passwordStorage.js */ +load("../../../resources/passwordStorage.js"); + +var kUser1 = "testpop3"; +var kUser2 = "testpop3a"; +var kProtocol = "pop3"; +var kHostname = "localhost"; +var kServerUrl = "mailbox://" + kHostname; + +add_task(async function () { + // Prepare files for passwords (generated by a script in bug 1018624). + await setupForPassword("signons-mailnews1.8-multiple.json"); + + // Set up the basic accounts and folders. + // We would use createPop3ServerAndLocalFolders() however we want to have + // a different username and NO password for this test (as we expect to load + // it from the signons json file in which the login information is stored). + localAccountUtils.loadLocalMailAccount(); + + let incomingServer1 = MailServices.accounts.createIncomingServer( + kUser1, + kHostname, + kProtocol + ); + + let incomingServer2 = MailServices.accounts.createIncomingServer( + kUser2, + kHostname, + kProtocol + ); + + // Test - Check there are two logins to begin with. + var logins = Services.logins.findLogins(kServerUrl, null, kServerUrl); + + Assert.equal(logins.length, 2); + + // These will either be one way around or the other. + if (logins[0].username == kUser1) { + Assert.equal(logins[1].username, kUser2); + } else { + Assert.equal(logins[0].username, kUser2); + Assert.equal(logins[1].username, kUser1); + } + + // Test - Remove a login via the incoming server + incomingServer1.forgetPassword(); + + logins = Services.logins.findLogins(kServerUrl, null, kServerUrl); + + // should be one login left for kUser2 + Assert.equal(logins.length, 1); + Assert.equal(logins[0].username, kUser2); + + // Bug 561056 - Expand username to also contain domain (i.e. full email). + incomingServer2.username = kUser2 + "@local.host"; + + logins = Services.logins.findLogins(kServerUrl, null, kServerUrl); + + // There should still be the one login left for kUser2 + Assert.equal(logins.length, 1); + // LoginInfo should be migrated in MsgIncomingServer.jsm. + Assert.equal(logins[0].username, incomingServer2.username); + + // Change username to another one. + incomingServer2.username = "testpop"; + logins = Services.logins.findLogins(kServerUrl, null, kServerUrl); + + // LoginInfo should be migrated in MsgIncomingServer.jsm. + Assert.equal(logins.length, 1); +}); diff --git a/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc1939.js b/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc1939.js new file mode 100644 index 0000000000..5a582649fa --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc1939.js @@ -0,0 +1,216 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Tests password failure with RFC1939. + * + * This test checks to see if the pop3 password failure is handled correctly. + * The steps are: + * - Have an invalid password in the password database. + * - Check we get a prompt asking what to do. + * - Check retry does what it should do. + * - Check cancel does what it should do. + * - Re-initiate connection, this time select enter new password, check that + * we get a new password prompt and can enter the password. + */ + +/* import-globals-from ../../../test/resources/alertTestUtils.js */ +/* import-globals-from ../../../test/resources/passwordStorage.js */ +load("../../../resources/alertTestUtils.js"); +load("../../../resources/passwordStorage.js"); + +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var server; +var daemon; +var incomingServer; +var attempt = 0; + +var kUserName = "testpop3"; +var kInvalidPassword = "pop3test"; +var kValidPassword = "testpop3"; + +add_setup(async function () { + // Enable debug for the sign on. + Services.prefs.setBoolPref("signon.debug", true); + + // Prepare files for passwords (generated by a script in bug 1018624). + await setupForPassword("signons-mailnews1.8.json"); + + registerAlertTestUtils(); + + // Set up the Server + daemon = new Pop3Daemon(); + function createHandler(d) { + var handler = new POP3_RFC1939_handler(d); + handler.dropOnAuthFailure = true; + + // Login information needs to match the one stored in the signons json file. + handler.kUsername = kUserName; + handler.kPassword = kValidPassword; + return handler; + } + server = new nsMailServer(createHandler, daemon); + server.start(); + + // Set up the basic accounts and folders. + // We would use createPop3ServerAndLocalFolders() however we want to have + // a different username and NO password for this test (as we expect to load + // it from the signons json file in which the login information is stored). + localAccountUtils.loadLocalMailAccount(); + + incomingServer = MailServices.accounts.createIncomingServer( + kUserName, + "localhost", + "pop3" + ); + + incomingServer.port = server.port; + + // Check that we haven't got any messages in the folder, if we have its a test + // setup issue. + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0); + + daemon.setMessages(["message1.eml"]); +}); + +add_task(async function getMail1() { + // Now get the mail. + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.pop3.GetNewMail( + gDummyMsgWindow, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + server.performTest(); + await Assert.rejects( + urlListener.promise, + reason => { + return reason === Cr.NS_ERROR_FAILURE; + }, + "Check that wrong password is entered and thrown" + ); + // We shouldn't have emails as the auth failed. + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0); + // Sanity check that we are at attempt 2. + Assert.equal(attempt, 2); + + // Check that we haven't forgotten the login even though we've retried and cancelled. + let logins = Services.logins.findLogins( + "mailbox://localhost", + null, + "mailbox://localhost" + ); + + Assert.equal(logins.length, 1); + Assert.equal(logins[0].username, kUserName); + Assert.equal(logins[0].password, kInvalidPassword); + + server.resetTest(); +}); + +add_task(async function getMail2() { + // Now get the mail + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.pop3.GetNewMail( + gDummyMsgWindow, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + server.performTest(); + await urlListener.promise; + Assert.equal(attempt, 4); + // On the last attempt (4th), we should have successfully got one mail. + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 1); + + // Now check the new one has been saved. + let logins = Services.logins.findLogins( + "mailbox://localhost", + null, + "mailbox://localhost" + ); + + Assert.equal(logins.length, 1); + Assert.equal(logins[0].username, kUserName); + Assert.equal(logins[0].password, kValidPassword); +}); + +add_task(function endTest() { + // Cleanup for potential Sockets/Ports leakage. + server.stop(); + server = null; + daemon = null; + incomingServer = null; + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } +}); + +/* exported alert, confirmEx, promptPasswordPS */ +function alertPS(parent, aDialogText, aText) { + // The first few attempts may prompt about the password problem, the last + // attempt shouldn't. + Assert.ok(attempt < 4); + + // Log the fact we've got an alert, but we don't need to test anything here. + info("Alert Title: " + aDialogText + "\nAlert Text: " + aText); +} + +function confirmExPS( + parent, + aDialogTitle, + aText, + aButtonFlags, + aButton0Title, + aButton1Title, + aButton2Title, + aCheckMsg, + aCheckState +) { + switch (++attempt) { + // First attempt, retry. + case 1: + info("Attempting retry"); + return 0; + // Second attempt, cancel. + case 2: + info("Cancelling login attempt"); + return 1; + // Third attempt, retry. + case 3: + info("Attempting Retry"); + return 0; + // Fourth attempt, enter a new password. + case 4: + info("Enter new password"); + return 2; + default: + throw new Error("unexpected attempt number " + attempt); + } +} + +/** + * Extension for alertTestUtils. + * Make sure that at the 4th attempt the correct password is used. + */ +function promptPasswordPS( + aParent, + aDialogTitle, + aText, + aPassword, + aCheckMsg, + aCheckState +) { + if (attempt == 4) { + aPassword.value = kValidPassword; + aCheckState.value = true; + return true; + } + return false; +} diff --git a/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc2449.js b/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc2449.js new file mode 100644 index 0000000000..1701f999c9 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc2449.js @@ -0,0 +1,215 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Tests password failure with RFC2449 auth. + * + * This test checks to see if the pop3 password failure is handled correctly. + * The steps are: + * - Have an invalid password in the password database. + * - Check we get a prompt asking what to do. + * - Check retry does what it should do. + * - Check cancel does what it should do. + * - Re-initiate connection, this time select enter new password, check that + * we get a new password prompt and can enter the password. + */ + +/* import-globals-from ../../../test/resources/alertTestUtils.js */ +/* import-globals-from ../../../test/resources/passwordStorage.js */ +load("../../../resources/alertTestUtils.js"); +load("../../../resources/passwordStorage.js"); + +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var server; +var daemon; +var incomingServer; +var attempt = 0; + +var kUserName = "testpop3"; +var kInvalidPassword = "pop3test"; +var kValidPassword = "testpop3"; + +add_setup(async function () { + // Enable debug for the sign on. + Services.prefs.setBoolPref("signon.debug", true); + + // Prepare files for passwords (generated by a script in bug 1018624). + await setupForPassword("signons-mailnews1.8.json"); + + registerAlertTestUtils(); + + // Set up the Server + daemon = new Pop3Daemon(); + function createHandler(d) { + var handler = new POP3_RFC2449_handler(d); + handler.dropOnAuthFailure = true; + + // Login information needs to match the one stored in the signons json file. + handler.kUsername = kUserName; + handler.kPassword = kValidPassword; + return handler; + } + server = new nsMailServer(createHandler, daemon); + server.start(); + + // Set up the basic accounts and folders. + // We would use createPop3ServerAndLocalFolders() however we want to have + // a different username and NO password for this test (as we expect to load + // it from the signons json file in which the login information is stored). + localAccountUtils.loadLocalMailAccount(); + + incomingServer = MailServices.accounts.createIncomingServer( + kUserName, + "localhost", + "pop3" + ); + + incomingServer.port = server.port; + + // Check that we haven't got any messages in the folder, if we have its a test + // setup issue. + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0); + + daemon.setMessages(["message1.eml"]); +}); + +add_task(async function getMail1() { + // Now get the mail. + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.pop3.GetNewMail( + gDummyMsgWindow, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + server.performTest(); + await Assert.rejects( + urlListener.promise, + reason => { + return reason === Cr.NS_ERROR_FAILURE; + }, + "Check that wrong password is entered and thrown" + ); + // We shouldn't have emails as the auth failed. + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0); + // Sanity check that we are at attempt 2. + Assert.equal(attempt, 2); + + // Check that we haven't forgotten the login even though we've retried and cancelled. + let logins = Services.logins.findLogins( + "mailbox://localhost", + null, + "mailbox://localhost" + ); + + Assert.equal(logins.length, 1); + Assert.equal(logins[0].username, kUserName); + Assert.equal(logins[0].password, kInvalidPassword); + + server.resetTest(); +}); + +add_task(async function getMail2() { + // Now get the mail. + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.pop3.GetNewMail( + gDummyMsgWindow, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + server.performTest(); + await urlListener.promise; + Assert.equal(attempt, 4); + // On the last attempt (4th), we should have successfully got one mail. + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 1); + + // Now check the new one has been saved. + let logins = Services.logins.findLogins( + "mailbox://localhost", + null, + "mailbox://localhost" + ); + + Assert.equal(logins.length, 1); + Assert.equal(logins[0].username, kUserName); + Assert.equal(logins[0].password, kValidPassword); +}); + +add_task(function endTest() { + // Cleanup for potential Sockets/Ports leakage. + server.stop(); + server = null; + daemon = null; + incomingServer = null; + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } +}); + +function alertPS(parent, aDialogText, aText) { + // The first few attempts may prompt about the password problem, the last + // attempt shouldn't. + Assert.ok(attempt < 4); + + // Log the fact we've got an alert, but we don't need to test anything here. + info("Alert Title: " + aDialogText + "\nAlert Text: " + aText); +} + +function confirmExPS( + parent, + aDialogTitle, + aText, + aButtonFlags, + aButton0Title, + aButton1Title, + aButton2Title, + aCheckMsg, + aCheckState +) { + switch (++attempt) { + // First attempt, retry. + case 1: + info("Attempting retry"); + return 0; + // Second attempt, cancel. + case 2: + info("Cancelling login attempt"); + return 1; + // Third attempt, retry. + case 3: + info("Attempting Retry"); + return 0; + // Fourth attempt, enter a new password. + case 4: + info("Enter new password"); + return 2; + default: + throw new Error("unexpected attempt number " + attempt); + } +} + +/** + * Extension for alertTestUtils. + * Make sure that at the 4th attempt the correct password is used. + */ +function promptPasswordPS( + aParent, + aDialogTitle, + aText, + aPassword, + aCheckMsg, + aCheckState +) { + if (attempt == 4) { + aPassword.value = kValidPassword; + aCheckState.value = true; + return true; + } + return false; +} diff --git a/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc5034.js b/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc5034.js new file mode 100644 index 0000000000..20e63d1358 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3PasswordFailure_rfc5034.js @@ -0,0 +1,217 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Tests password failure with RFC5034 auth. + * + * This test checks to see if the pop3 password failure is handled correctly + * in the case of the server dropping the connection during auth login. + * We use POP3_RFC5034_handler so auth=login will be supported. + * The steps are: + * - Have an invalid password in the password database. + * - Check we get a prompt asking what to do. + * - Check retry does what it should do. + * - Check cancel does what it should do. + * - Re-initiate connection, this time select enter new password, check that + * we get a new password prompt and can enter the password. + */ + +/* import-globals-from ../../../test/resources/alertTestUtils.js */ +/* import-globals-from ../../../test/resources/passwordStorage.js */ +load("../../../resources/alertTestUtils.js"); +load("../../../resources/passwordStorage.js"); + +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var server; +var daemon; +var incomingServer; +var attempt = 0; + +var kUserName = "testpop3"; +var kInvalidPassword = "pop3test"; +var kValidPassword = "testpop3"; + +add_setup(async function () { + // Enable debug for the sign on. + Services.prefs.setBoolPref("signon.debug", true); + + // Prepare files for passwords (generated by a script in bug 1018624). + await setupForPassword("signons-mailnews1.8.json"); + + registerAlertTestUtils(); + + // Set up the Server + daemon = new Pop3Daemon(); + function createHandler(d) { + var handler = new POP3_RFC5034_handler(d); + handler.dropOnAuthFailure = true; + // Login information needs to match the one stored in the signons json file. + handler.kUsername = kUserName; + handler.kPassword = kValidPassword; + return handler; + } + server = new nsMailServer(createHandler, daemon); + server.start(); + + // Set up the basic accounts and folders. + // We would use createPop3ServerAndLocalFolders() however we want to have + // a different username and NO password for this test (as we expect to load + // it from the signons json file in which the login information is stored). + localAccountUtils.loadLocalMailAccount(); + + incomingServer = MailServices.accounts.createIncomingServer( + kUserName, + "localhost", + "pop3" + ); + + incomingServer.port = server.port; + + // Check that we haven't got any messages in the folder, if we have its a test + // setup issue. + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0); + + daemon.setMessages(["message1.eml"]); +}); + +add_task(async function getMail1() { + // Now get the mail. + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.pop3.GetNewMail( + gDummyMsgWindow, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + server.performTest(); + await Assert.rejects( + urlListener.promise, + reason => { + return reason === Cr.NS_ERROR_FAILURE; + }, + "Check that wrong password is entered and thrown" + ); + // We shouldn't have emails as the auth failed. + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0); + // Sanity check that we are at attempt 2. + Assert.equal(attempt, 2); + + // Check that we haven't forgotten the login even though we've retried and cancelled. + let logins = Services.logins.findLogins( + "mailbox://localhost", + null, + "mailbox://localhost" + ); + + Assert.equal(logins.length, 1); + Assert.equal(logins[0].username, kUserName); + Assert.equal(logins[0].password, kInvalidPassword); + + server.resetTest(); +}); + +add_task(async function getMail2() { + // Now get the mail. + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.pop3.GetNewMail( + gDummyMsgWindow, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + server.performTest(); + await urlListener.promise; + Assert.equal(attempt, 4); + // On the last attempt (4th), we should have successfully got one mail. + Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 1); + + // Now check the new one has been saved. + let logins = Services.logins.findLogins( + "mailbox://localhost", + null, + "mailbox://localhost" + ); + + Assert.equal(logins.length, 1); + Assert.equal(logins[0].username, kUserName); + Assert.equal(logins[0].password, kValidPassword); +}); + +add_task(function endTest() { + // Cleanup for potential Sockets/Ports leakage. + server.stop(); + server = null; + daemon = null; + incomingServer = null; + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } +}); + +/* exported alert, confirmEx, promptPasswordPS */ +function alertPS(parent, aDialogText, aText) { + // The first few attempts may prompt about the password problem, the last + // attempt shouldn't. + Assert.ok(attempt < 4); + + // Log the fact we've got an alert, but we don't need to test anything here. + info("Alert Title: " + aDialogText + "\nAlert Text: " + aText); +} + +function confirmExPS( + parent, + aDialogTitle, + aText, + aButtonFlags, + aButton0Title, + aButton1Title, + aButton2Title, + aCheckMsg, + aCheckState +) { + switch (++attempt) { + // First attempt, retry. + case 1: + info("Attempting retry"); + return 0; + // Second attempt, cancel. + case 2: + info("Cancelling login attempt"); + return 1; + // Third attempt, retry. + case 3: + info("Attempting Retry"); + return 0; + // Fourth attempt, enter a new password. + case 4: + info("Enter new password"); + return 2; + default: + throw new Error("unexpected attempt number " + attempt); + } +} + +/** + * Extension for alertTestUtils. + * Make sure that at the 4th attempt the correct password is used. + */ +function promptPasswordPS( + aParent, + aDialogTitle, + aText, + aPassword, + aCheckMsg, + aCheckState +) { + if (attempt == 4) { + aPassword.value = kValidPassword; + aCheckState.value = true; + return true; + } + return false; +} diff --git a/comm/mailnews/local/test/unit/test_pop3Proxy.js b/comm/mailnews/local/test/unit/test_pop3Proxy.js new file mode 100644 index 0000000000..248bbaf92b --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3Proxy.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +// Test that POP3 over a proxy works. + +const { NetworkTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/NetworkTestUtils.jsm" +); +const { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +const PORT = 110; + +var server, daemon, incomingServer; + +add_setup(async function () { + // Disable new mail notifications + Services.prefs.setBoolPref("mail.biff.play_sound", false); + Services.prefs.setBoolPref("mail.biff.show_alert", false); + Services.prefs.setBoolPref("mail.biff.show_tray_icon", false); + Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false); + + [daemon, server] = setupServerDaemon(); + server.start(); + NetworkTestUtils.configureProxy("pop.tinderbox.invalid", PORT, server.port); + + // Set up the basic accounts and folders + incomingServer = createPop3ServerAndLocalFolders( + PORT, + "pop.tinderbox.invalid" + ); + + // Add a message to download + daemon.setMessages(["message1.eml"]); +}); + +add_task(async function downloadEmail() { + // Check that we haven't got any messages in the folder, if we have its a test + // setup issue. + equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0); + + // Now get the mail + let urlListener = new PromiseTestUtils.PromiseUrlListener(); + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + await urlListener.promise; + + // We downloaded a message, so it works! + equal(localAccountUtils.inboxFolder.getTotalMessages(false), 1); +}); + +add_task(async function cleanUp() { + NetworkTestUtils.shutdownServers(); + incomingServer.closeCachedConnections(); + server.stop(); +}); diff --git a/comm/mailnews/local/test/unit/test_pop3Pump.js b/comm/mailnews/local/test/unit/test_pop3Pump.js new file mode 100644 index 0000000000..239d284abb --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3Pump.js @@ -0,0 +1,32 @@ +/** + * The intent of this file is to demonstrate a minimal + * POP3 unit test using the testing file POP3Pump.js + */ +/* import-globals-from ../../../test/resources/POP3pump.js */ +load("../../../resources/POP3pump.js"); + +var testSubjects = [ + "[Bug 397009] A filter will let me tag, but not untag", + "Hello, did you receive my bugmail?", +]; + +add_task(async function runPump() { + // demonstration of access to the local inbox folder + dump( + "local inbox folder " + localAccountUtils.inboxFolder.URI + " is loaded\n" + ); + // demonstration of access to the fake server + dump("Server " + gPOP3Pump.fakeServer.prettyName + " is loaded\n"); + + gPOP3Pump.files = ["../../../data/bugmail1", "../../../data/draft1"]; + await gPOP3Pump.run(); + + // get message headers for the inbox folder + var msgCount = 0; + for (let hdr of localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()) { + msgCount++; + Assert.equal(hdr.subject, testSubjects[msgCount - 1]); + } + Assert.equal(msgCount, 2); + gPOP3Pump = null; +}); diff --git a/comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMDisconnect.js b/comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMDisconnect.js new file mode 100644 index 0000000000..ff13870352 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMDisconnect.js @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Server which advertises CRAM-MD5, but is impolite enough to just + * disconnect (close the TCP connection) when we try it. + * + * This is a tough one, because we may lose state on which auth schemes + * are allowed and which ones failed and may restart from scratch, and + * retry, never skipping the failed scheme. + * Dear server implementors, NEVER DO THAT! Be polite, give an error + * with explanation and by all means keep the connection open. + * + * I don't know if real servers do that, but bienvenu says they exist. + * + * TODO: + * This test shows that the current situation is not good. + * Problems: + * - We should reopen the connection, remember which auth scheme failed + * and start with the next in list, not trying the broken one again. + * We currently neither retry nor remember. + * - incomingServer thinks it is still running/busy although the connection is + * clearly done and over. + * + * @author Ben Bucksch + */ + +var server; +var daemon; +var incomingServer; +test = + "Server which advertises CRAM-MD5, but closes the connection when it's tried"; +// that's how it currently looks like (we fail to log in): +var expectedTransaction = ["AUTH", "CAPA", "AUTH CRAM-MD5"]; +// TODO that's how it should look like (we start a new connection and try another scheme): +// const expectedTransaction = ["AUTH", "CAPA", "AUTH CRAM-MD5", "CAPA", "AUTH PLAIN", "STAT"]; + +var urlListener = { + OnStartRunningUrl(url) {}, + OnStopRunningUrl(url, result) { + try { + // We should be getting an error here, because we couldn't log in. + Assert.equal(result, Cr.NS_ERROR_FAILURE); + + var transaction = server.playTransaction(); + do_check_transaction(transaction, expectedTransaction); + + do_timeout(0, endTest); + } catch (e) { + server.stop(); + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_throw(e); + } + }, +}; + +function endTest() { + // No more tests, let everything finish + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_test_finished(); +} + +function CRAMFail_handler(daemon_) { + POP3_RFC5034_handler.call(this, daemon_); + + this._kAuthSchemeStartFunction["CRAM-MD5"] = this.killConn; +} +CRAMFail_handler.prototype = { + __proto__: POP3_RFC5034_handler.prototype, // inherit + + killConn() { + this.closing = true; + return "-ERR I don't feel like it"; + }, +}; + +function run_test() { + try { + do_test_pending(); + + // Disable new mail notifications + Services.prefs.setBoolPref("mail.biff.play_sound", false); + Services.prefs.setBoolPref("mail.biff.show_alert", false); + Services.prefs.setBoolPref("mail.biff.show_tray_icon", false); + Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false); + + daemon = new Pop3Daemon(); + function createHandler(d) { + return new CRAMFail_handler(d); + } + server = new nsMailServer(createHandler, daemon); + server.start(); + + incomingServer = createPop3ServerAndLocalFolders(server.port); + let msgServer = incomingServer; + msgServer.QueryInterface(Ci.nsIMsgIncomingServer); + // Need to allow any auth here, although that's not use in TB really, + // because we need to fall back to something after CRAM-MD5 and + // check that login works after we fell back. + msgServer.authMethod = Ci.nsMsgAuthMethod.anything; + + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + server.performTest(); + } catch (e) { + server.stop(); + + do_throw(e); + } finally { + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +} diff --git a/comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMFail.js b/comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMFail.js new file mode 100644 index 0000000000..105f3fb0a0 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_pop3ServerBrokenCRAMFail.js @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/** + * Server which advertises CRAM-MD5, but fails when it's tried. + * This reportedly happens for some misconfigured servers. + */ + +var server; +var daemon; +var incomingServer; +test = "Server which advertises CRAM-MD5, but fails when it's tried"; +var expectedTransaction = [ + "AUTH", + "CAPA", + "AUTH CRAM-MD5", + "AUTH PLAIN", + "STAT", +]; + +const kStateAuthNeeded = 1; // the same value as in Pop3d.jsm + +var urlListener = { + OnStartRunningUrl(url) {}, + OnStopRunningUrl(url, result) { + try { + Assert.equal(result, 0); + + var transaction = server.playTransaction(); + do_check_transaction(transaction, expectedTransaction); + + do_timeout(0, checkBusy); + } catch (e) { + server.stop(); + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_throw(e); + } + }, +}; + +function checkBusy() { + // If the server hasn't quite finished, just delay a little longer. + if (incomingServer.serverBusy) { + do_timeout(20, checkBusy); + return; + } + + endTest(); +} + +function endTest() { + incomingServer.closeCachedConnections(); + + // No more tests, let everything finish + server.stop(); + + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + + do_test_finished(); +} + +function CRAMFail_handler(daemon_) { + POP3_RFC5034_handler.call(this, daemon_); + + this._kAuthSchemeStartFunction["CRAM-MD5"] = this.killConn; +} +CRAMFail_handler.prototype = { + __proto__: POP3_RFC5034_handler.prototype, // inherit + + killConn() { + this._multiline = false; + this._state = kStateAuthNeeded; + return "-ERR I just pretended to implement CRAM-MD5"; + }, +}; + +function run_test() { + try { + do_test_pending(); + + // Disable new mail notifications + Services.prefs.setBoolPref("mail.biff.play_sound", false); + Services.prefs.setBoolPref("mail.biff.show_alert", false); + Services.prefs.setBoolPref("mail.biff.show_tray_icon", false); + Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false); + + daemon = new Pop3Daemon(); + function createHandler(d) { + return new CRAMFail_handler(d); + } + server = new nsMailServer(createHandler, daemon); + server.start(); + + incomingServer = createPop3ServerAndLocalFolders(server.port); + let msgServer = incomingServer; + msgServer.QueryInterface(Ci.nsIMsgIncomingServer); + // Need to allow any auth here, although that's not use in TB really, + // because we need to fall back to something after CRAM-MD5 and + // check that login works after we fell back. + msgServer.authMethod = Ci.nsMsgAuthMethod.anything; + + MailServices.pop3.GetNewMail( + null, + urlListener, + localAccountUtils.inboxFolder, + incomingServer + ); + server.performTest(); + } catch (e) { + server.stop(); + + do_throw(e); + } finally { + var thread = gThreadManager.currentThread; + while (thread.hasPendingEvents()) { + thread.processNextEvent(true); + } + } +} diff --git a/comm/mailnews/local/test/unit/test_preview.js b/comm/mailnews/local/test/unit/test_preview.js new file mode 100644 index 0000000000..83cfce256b --- /dev/null +++ b/comm/mailnews/local/test/unit/test_preview.js @@ -0,0 +1,40 @@ +var bugmail10 = do_get_file("../../../data/bugmail10"); +var bugmail11 = do_get_file("../../../data/bugmail11"); +var bugmail10_preview = + "Do not reply to this email. You can add comments to this bug at https://bugzilla.mozilla.org/show_bug.cgi?id=436880 -- Configure bugmail: https://bugzilla.mozilla.org/userprefs.cgi?tab=email ------- You are receiving this mail because: -----"; +var bugmail11_preview = + "Bugzilla has received a request to create a user account using your email address (example@example.org). To confirm that you want to create an account using that email address, visit the following link: https://bugzilla.mozilla.org/token.cgi?t=xxx"; + +function run_test() { + do_test_pending(); + copyFileMessageInLocalFolder(bugmail10, 0, "", null, copy_next_message); +} + +function copy_next_message(aMessageHeaderKeys, aStatus) { + copyFileMessageInLocalFolder(bugmail11, 0, "", null, test_preview); +} + +function test_preview(aMessageHeaderKeys, aStatus) { + let headerKeys = aMessageHeaderKeys; + Assert.notEqual(headerKeys, null); + Assert.equal(headerKeys.length, 2); + try { + localAccountUtils.inboxFolder.fetchMsgPreviewText(headerKeys, null); + Assert.equal( + localAccountUtils.inboxFolder + .GetMessageHeader(headerKeys[0]) + .getStringProperty("preview"), + bugmail10_preview + ); + Assert.equal( + localAccountUtils.inboxFolder + .GetMessageHeader(headerKeys[1]) + .getStringProperty("preview"), + bugmail11_preview + ); + } catch (ex) { + dump(ex); + do_throw(ex); + } + do_test_finished(); +} diff --git a/comm/mailnews/local/test/unit/test_saveMessage.js b/comm/mailnews/local/test/unit/test_saveMessage.js new file mode 100644 index 0000000000..e488a0ed5b --- /dev/null +++ b/comm/mailnews/local/test/unit/test_saveMessage.js @@ -0,0 +1,69 @@ +/** + * Test bug 460636 - Saving message in local folder as .EML removes starting dot in all lines, and ignores line if single dot only line. + */ + +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var MSG_LINEBREAK = "\r\n"; +var dot = do_get_file("data/dot"); +var saveFile = Services.dirsvc.get("TmpD", Ci.nsIFile); +saveFile.append(dot.leafName + ".eml"); +saveFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + +function run_test() { + registerCleanupFunction(teardown); + do_test_pending(); + do_timeout(10000, function () { + do_throw( + "SaveMessageToDisk did not complete within 10 seconds" + + "(incorrect messageURI?). ABORTING." + ); + }); + copyFileMessageInLocalFolder(dot, 0, "", null, save_message); +} + +async function save_message(aMessageHeaderKeys, aStatus) { + let headerKeys = aMessageHeaderKeys; + Assert.notEqual(headerKeys, null); + + let message = localAccountUtils.inboxFolder.GetMessageHeader(headerKeys[0]); + let msgURI = localAccountUtils.inboxFolder.getUriForMsg(message); + let messageService = Cc[ + "@mozilla.org/messenger/messageservice;1?type=mailbox-message" + ].getService(Ci.nsIMsgMessageService); + let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener(); + messageService.SaveMessageToDisk( + msgURI, + saveFile, + false, + promiseUrlListener, + {}, + true, + null + ); + await promiseUrlListener.promise; + check_each_line( + await IOUtils.readUTF8(dot.path), + await IOUtils.readUTF8(saveFile.path) + ); + do_test_finished(); +} + +function check_each_line(aExpectedLines, aActualLines) { + let expectedStrings = aExpectedLines.split(MSG_LINEBREAK); + let actualStrings = aActualLines.split(MSG_LINEBREAK); + + expectedStrings.shift(); + Assert.equal(expectedStrings.length, actualStrings.length); + for (let line = 0; line < expectedStrings.length; line++) { + Assert.equal(expectedStrings[line], actualStrings[line]); + } +} + +function teardown() { + if (saveFile.exists()) { + saveFile.remove(false); + } +} diff --git a/comm/mailnews/local/test/unit/test_streamHeaders.js b/comm/mailnews/local/test/unit/test_streamHeaders.js new file mode 100644 index 0000000000..a1e7ef1640 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_streamHeaders.js @@ -0,0 +1,90 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This mainly tests that streamHeaders does not result in the crash + * of bug 752768 + * + * adapted from test_pop3Pump.js by Kent James <kent@caspia.com> + */ + +/* import-globals-from ../../../test/resources/alertTestUtils.js */ +load("../../../resources/alertTestUtils.js"); + +/* import-globals-from ../../../test/resources/POP3pump.js */ +load("../../../resources/POP3pump.js"); + +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var testSubjects = ["Hello, did you receive my bugmail?"]; + +var gHdr; + +add_task(async function loadMessages() { + let pop3Resolve; + let pop3Promise = new Promise(resolve => { + pop3Resolve = resolve; + }); + gPOP3Pump.files = ["../../../data/draft1"]; + gPOP3Pump.onDone = pop3Resolve; + gPOP3Pump.run(); + await pop3Promise; + + // Get message headers for the inbox folder. + var msgCount = 0; + for (gHdr of localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()) { + msgCount++; + Assert.equal(gHdr.subject, testSubjects[msgCount - 1]); + } + Assert.equal(msgCount, 1); + gPOP3Pump = null; +}); + +add_task(async function goodStreaming() { + // Try to stream the headers of the last message. + let uri = gHdr.folder.getUriForMsg(gHdr); + let messageService = MailServices.messageServiceFromURI(uri); + let streamListener = new PromiseTestUtils.PromiseStreamListener(); + messageService.streamHeaders(uri, streamListener, null, true); + // The message contains this header. + let streamData = await streamListener.promise; + Assert.ok( + streamData.includes( + "X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0" + ) + ); +}); + +/** + * Crash from bug 752768. + */ +add_task(async function badStreaming() { + // Try to stream the headers of the last message. + let folder = gHdr.folder; + let uri = folder.getUriForMsg(gHdr); + + let dbFile = folder.summaryFile; + // Force an invalid database. + folder.msgDatabase.forceClosed(); + dbFile.remove(false); + folder.msgDatabase = null; + + let messageService = MailServices.messageServiceFromURI(uri); + let haveError = false; + try { + let streamListener = new PromiseTestUtils.PromiseStreamListener(); + messageService.streamHeaders(uri, streamListener, null, true); + await streamListener.promise; + } catch (e) { + // Should throw NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE (0x80550005). + haveError = true; + } finally { + Assert.ok( + haveError, + "Ensure that the stream crashes with NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE" + ); + } +}); diff --git a/comm/mailnews/local/test/unit/test_undoDelete.js b/comm/mailnews/local/test/unit/test_undoDelete.js new file mode 100644 index 0000000000..a3a52e2fd5 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_undoDelete.js @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests undoing of an local folder message deleted to the trash. +// +// Original Author: David Bienvenu <dbienvenu@mozilla.com> + +// Globals +let gMsg1; +let gMessages = []; +let gMsgWindow; +let gCurTestNum; +let gMsgId1; +let gTestFolder; + +var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageGenerator.jsm" +); +var { MessageInjection } = ChromeUtils.import( + "resource://testing-common/mailnews/MessageInjection.jsm" +); +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); + +var messageInjection = new MessageInjection({ mode: "local" }); + +add_setup(async function () { + gMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance( + Ci.nsIMsgWindow + ); + + var messageGenerator = new MessageGenerator(); + gMsg1 = messageGenerator.makeMessage(); + let msg2 = messageGenerator.makeMessage({ inReplyTo: gMsg1 }); + + let messages = []; + messages = messages.concat([gMsg1, msg2]); + let msgSet = new SyntheticMessageSet(messages); + gTestFolder = await messageInjection.makeEmptyFolder(); + await messageInjection.addSetsToFolders([gTestFolder], [msgSet]); +}); + +add_task(async function deleteMessage() { + let msgToDelete = mailTestUtils.firstMsgHdr(gTestFolder); + gMsgId1 = msgToDelete.messageId; + gMessages.push(msgToDelete); + let copyListener = new PromiseTestUtils.PromiseCopyListener(); + gTestFolder.deleteMessages( + gMessages, + gMsgWindow, + false, + true, + copyListener, + true + ); + await copyListener.promise; +}); + +add_task(async function undoDelete() { + gMsgWindow.transactionManager.undoTransaction(); + // There's no listener for this, so we'll just have to wait a little. + await PromiseTestUtils.promiseDelay(1500); +}); + +add_task(function verifyFolders() { + let msgRestored = gTestFolder.msgDatabase.getMsgHdrForMessageID(gMsgId1); + let msg = mailTestUtils.loadMessageToString(gTestFolder, msgRestored); + Assert.equal(msg, gMsg1.toMboxString()); +}); + +add_task(function endTest() { + // Cleanup, null out everything. + gMessages = []; + gMsgWindow.closeWindow(); + gMsgWindow = null; + localAccountUtils.inboxFolder = null; + localAccountUtils.incomingServer = null; +}); diff --git a/comm/mailnews/local/test/unit/test_verifyLogon.js b/comm/mailnews/local/test/unit/test_verifyLogon.js new file mode 100644 index 0000000000..6993be7203 --- /dev/null +++ b/comm/mailnews/local/test/unit/test_verifyLogon.js @@ -0,0 +1,93 @@ +/** + * This test checks to see if the pop3 verify logon handles password failure correctly. + * The steps are: + * - Set an invalid password on the server object. + * - Check that verifyLogon fails + */ + +/* import-globals-from ../../../test/resources/alertTestUtils.js */ +load("../../../resources/alertTestUtils.js"); + +var server; +var daemon; +var incomingServer; + +var kUserName = "testpop3"; +var kInvalidPassword = "pop3test"; +var kValidPassword = "testpop3"; + +function verifyPop3Logon(validPassword) { + incomingServer.password = validPassword ? kValidPassword : kInvalidPassword; + urlListener.expectSuccess = validPassword; + let uri = incomingServer.verifyLogon(urlListener, gDummyMsgWindow); + // clear msgWindow so url won't prompt for passwords. + uri.QueryInterface(Ci.nsIMsgMailNewsUrl).msgWindow = null; + + server.performTest(); + return false; +} + +var urlListener = { + expectSucess: false, + OnStartRunningUrl(url) {}, + OnStopRunningUrl(url, aResult) { + Assert.equal(Components.isSuccessCode(aResult), this.expectSuccess); + }, +}; + +function actually_run_test() { + daemon.setMessages(["message1.eml"]); + + // check that verifyLogon fails with bad password + verifyPop3Logon(false); + + dump("\nverify logon false 1\n"); + do_timeout(1000, verifyGoodLogon); +} + +function verifyGoodLogon() { + server.resetTest(); + + // check that verifyLogon succeeds with good password + verifyPop3Logon(true); + + dump("\nverify logon true 1\n"); + do_test_finished(); +} + +function run_test() { + // Disable new mail notifications + Services.prefs.setBoolPref("mail.biff.play_sound", false); + Services.prefs.setBoolPref("mail.biff.show_alert", false); + Services.prefs.setBoolPref("mail.biff.show_tray_icon", false); + Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false); + // Set up the Server + daemon = new Pop3Daemon(); + function createHandler(d) { + var handler = new POP3_RFC1939_handler(d); + // Login information needs to match the one stored in the signons json file. + handler.kUsername = kUserName; + handler.kPassword = kValidPassword; + handler.dropOnAuthFailure = true; + return handler; + } + server = new nsMailServer(createHandler, daemon); + server.start(); + + // Set up the basic accounts and folders. + // We would use createPop3ServerAndLocalFolders() however we want to have + // a different username and NO password for this test (as we expect to load + // it from the signons json file in which the login information is stored). + localAccountUtils.loadLocalMailAccount(); + + incomingServer = MailServices.accounts.createIncomingServer( + kUserName, + "localhost", + "pop3" + ); + incomingServer.port = server.port; + + do_test_pending(); + + actually_run_test(); +} diff --git a/comm/mailnews/local/test/unit/xpcshell.ini b/comm/mailnews/local/test/unit/xpcshell.ini new file mode 100644 index 0000000000..43fd637fe3 --- /dev/null +++ b/comm/mailnews/local/test/unit/xpcshell.ini @@ -0,0 +1,57 @@ +[DEFAULT] +head = head_maillocal.js +tail = +support-files = data/* +prefs = + mail.biff.play_sound=false + mail.biff.show_alert=false + mail.biff.show_tray_icon=false + mail.biff.animate_dock_icon=false + +[test_bug457168.js] +[test_duplicateKey.js] +[test_fileName.js] +[test_folderLoaded.js] +[test_localFolder.js] +[test_mailboxContentLength.js] +[test_mailboxProtocol.js] +[test_mailboxURL.js] +[test_msgCopy.js] +[test_msgIDParsing.js] +[test_noTop.js] +[test_noUidl.js] +[test_nsIMsgLocalMailFolder.js] +[test_nsIMsgParseMailMsgState.js] +[test_nsIMsgPluggableStore.js] +[test_over2GBMailboxes.js] +[test_over4GBMailboxes.js] +# This one needs a longer timeout for working with a 4GiB file. +requesttimeoutfactor = 2 +[test_Pop3Channel.js] +[test_pop3AuthMethods.js] +[test_pop3Client.js] +[test_pop3Download.js] +[test_pop3DownloadTempFileHandling.js] +[test_pop3Duplicates.js] +[test_pop3FilterActions.js] +[test_pop3Filters.js] +[test_pop3GetNewMail.js] +[test_pop3GSSAPIFail.js] +[test_pop3MoveFilter.js] +[test_pop3MoveFilter2.js] +[test_pop3MultiCopy.js] +[test_pop3MultiCopy2.js] +[test_pop3Password.js] +[test_pop3Password2.js] +skip-if = true # realhostname and realuserName don't exist anymore +[test_pop3Password3.js] +[test_pop3PasswordFailure_rfc1939.js] +[test_pop3PasswordFailure_rfc2449.js] +[test_pop3PasswordFailure_rfc5034.js] +[test_pop3Proxy.js] +[test_pop3Pump.js] +[test_preview.js] +[test_saveMessage.js] +[test_streamHeaders.js] +[test_undoDelete.js] +[test_verifyLogon.js] |