Skip to content

Commit

Permalink
Merge pull request #132 from requaos/requaos/chardetcharlimit
Browse files Browse the repository at this point in the history
#131 include rune count minimum for chardet
  • Loading branch information
requaos committed Aug 30, 2019
2 parents 0f4ede2 + 221d76c commit 51ed44e
Show file tree
Hide file tree
Showing 5 changed files with 1,626 additions and 8 deletions.
9 changes: 6 additions & 3 deletions part.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import (
"github.com/pkg/errors"
)

const minCharsetConfidence = 85
const (
minCharsetConfidence = 85
minCharsetRuneLength = 100
)

// Part represents a node in the MIME multipart tree. The Content-Type, Disposition and File Name
// are parsed out of the header for easier access.
Expand Down Expand Up @@ -192,8 +195,8 @@ func (p *Part) convertFromDetectedCharset(r io.Reader) (io.Reader, error) {
// Restore r.
r = bytes.NewReader(buf)

if cs == nil || cs.Confidence < minCharsetConfidence {
// Low confidence, use declared character set.
if cs == nil || cs.Confidence < minCharsetConfidence || len(bytes.Runes(buf)) < minCharsetRuneLength {
// Low confidence or not enough characters, use declared character set.
return p.convertFromStatedCharset(r), nil
}

Expand Down
95 changes: 90 additions & 5 deletions part_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ func TestMultiMixedParts(t *testing.T) {
Parent: test.PartExists,
NextSibling: test.PartExists,
ContentType: "text/plain",
Charset: "ISO-8859-1",
Charset: "us-ascii",
PartID: "1",
}
test.ComparePart(t, p, wantp)
Expand All @@ -341,7 +341,7 @@ func TestMultiMixedParts(t *testing.T) {
wantp = &enmime.Part{
Parent: test.PartExists,
ContentType: "text/plain",
Charset: "ISO-8859-1",
Charset: "us-ascii",
PartID: "2",
}
test.ComparePart(t, p, wantp)
Expand Down Expand Up @@ -379,7 +379,7 @@ func TestMultiOtherParts(t *testing.T) {
Parent: test.PartExists,
NextSibling: test.PartExists,
ContentType: "text/plain",
Charset: "ISO-8859-1",
Charset: "us-ascii",
PartID: "1",
}
test.ComparePart(t, p, wantp)
Expand All @@ -392,7 +392,7 @@ func TestMultiOtherParts(t *testing.T) {
wantp = &enmime.Part{
Parent: test.PartExists,
ContentType: "text/plain",
Charset: "ISO-8859-1",
Charset: "us-ascii",
PartID: "2",
}
test.ComparePart(t, p, wantp)
Expand Down Expand Up @@ -523,7 +523,7 @@ func TestPartSimilarBoundary(t *testing.T) {
Parent: test.PartExists,
NextSibling: test.PartExists,
ContentType: "text/plain",
Charset: "ISO-8859-1",
Charset: "us-ascii",
PartID: "1",
}
test.ComparePart(t, p, wantp)
Expand Down Expand Up @@ -970,4 +970,89 @@ func TestChardetFailure(t *testing.T) {
test.ComparePart(t, p, wantp)
test.ContentEqualsString(t, p.Content, expectedContent)
})

t.Run("not enough characters part", func(t *testing.T) {
r := test.OpenTestData("parts", "chardet-fail-not-long-enough.raw")
p, err := enmime.ReadParts(r)
if err != nil {
t.Fatal(err)
}
if len(p.Errors) > 0 {
t.Errorf("Errors encountered while processing part: %v", p.Errors)
}
wantp := &enmime.Part{
PartID: "0",
ContentType: "text/plain",
Charset: "UTF-8",
}
test.ComparePart(t, p, wantp)
test.ContentEqualsString(t, p.Content, "和弟弟\r\n")
})
}

func TestChardetSuccess(t *testing.T) {
// Testdata in these tests licensed under CC0: Public Domain
t.Run("big-5 data in us-ascii part", func(t *testing.T) {
r := test.OpenTestData("parts", "chardet-success-big-5.raw")
p, err := enmime.ReadParts(r)
if err != nil {
t.Fatal(err)
}
expectedErr := enmime.Error{
Name: "Character Set Declaration Mismatch",
Detail: "declared charset \"us-ascii\", detected \"Big5\", confidence 100",
Severe: false,
}
foundExpectedErr := false
if len(p.Errors) > 0 {
for _, v := range p.Errors {
if *v == expectedErr {
foundExpectedErr = true
} else {
t.Errorf("Error encountered while processing part: %v", v)
}
}
}
if !foundExpectedErr {
t.Errorf("Expected to find %v warning", expectedErr)
}
wantp := &enmime.Part{
PartID: "0",
ContentType: "text/plain",
Charset: "Big5",
}
test.ComparePart(t, p, wantp)
})

t.Run("iso-8859-1 data in us-ascii part", func(t *testing.T) {
r := test.OpenTestData("parts", "chardet-success-iso-8859-1.raw")
p, err := enmime.ReadParts(r)
if err != nil {
t.Fatal(err)
}
expectedErr := enmime.Error{
Name: "Character Set Declaration Mismatch",
Detail: "declared charset \"us-ascii\", detected \"ISO-8859-1\", confidence 90",
Severe: false,
}
foundExpectedErr := false
if len(p.Errors) > 0 {
for _, v := range p.Errors {
if *v == expectedErr {
foundExpectedErr = true
} else {
t.Errorf("Error encountered while processing part: %v", v)
}
}
}
if !foundExpectedErr {
t.Errorf("Expected to find %v warning", expectedErr)
}
wantp := &enmime.Part{
PartID: "0",
ContentType: "text/plain",
Charset: "ISO-8859-1",
}
test.ComparePart(t, p, wantp)
})
}
4 changes: 4 additions & 0 deletions testdata/parts/chardet-fail-not-long-enough.raw
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: base64

5ZKM5byf5byfDQo=
204 changes: 204 additions & 0 deletions testdata/parts/chardet-success-big-5.raw
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: base64

w0OsT65hsFYgIKVfu/QgIMNDpKexwA0KDQqo97LEpEOhQK21w+OhQML4w8ChQLLX
qO4NCg0KrbXD47LEpFGkSw0KDQogICAgpNKkRaZ7pKekSKFBqKW7eaSjplChQaXN
pcGkd6jToUGpVLFgtU2ob6FDptuhbaxLrO6hbrzQu/SopaSnDQq2x6FBoXHC98TM
oXKl2Lehtfykp7hnoUGmubtcqOS4+6n6pKeq7KRdoUOr4aaztK22r7XboW2k6Kil
oW6hQajkDQqopaRqs8ahQ7VNrNKm0qZXqqukp6ZQsqehQaSjxePBbsWqpKesT6tE
pF2hQ7ZlvkelyKpgpLu4Z6FBsKq7pLjRDQqhbadmxP2hbqFCoW2yYatuoW6hQbNc
t1azeaFtu6Gk5aFuoUG8Qr9Ru3OhbcTAplehbqFBqWyms8S0qnCwssLHDQqlSMPS
rbWmcqbVoUOm06Vqu3m7UKS1ru2nT6FBqOS2obu0rauyTb9CoUG1U6W8pWm+5aFG
pVulSKS6qKWlfqilDQqhQqvmqKWufailoULFqq1ZpKfD/qFBr3Goz6RIusOhQ65d
qPuopbPQoW26uLaurbW4caFuoUGsT7p+pb2kSL9XDQqqvqTPu3mhQ6bcqfPDUaVA
oUGmuajGpGqm5qFDsKq2UbZtpL2ko7jRpM+7eaFBpUissCAgqceyp6FDptuv97PW
DQqr4aFBrbXD/b5XpVihQaZVprOkZ623oUG7vKzbq0SvuqFBq/ywqKSnv9mhQaW8
qr6xRaxPoUOmQKVIq9Kk/bOjDQqotqFBsNGu1aToq1WhQabS8vKlaqS1oUGssKSn
p+mwSqFD3Xym07ZxpKehQb9XqvezrrtQrKWkVabVoUMNCiAgICCrbqTopPSkZ6lN
rFihQajkrbWyTcF8ptOkwbjaoUGloqZir0KyTKFBqOTD46Zou8CrVaFDpV+k6KRz
pHQNCrJgq3ChQajkrbWoSL9CptMgILZ3oUGxb6jkveiqvaFBqOTD46ZopWq7eaFD
tU2rYbDDp2ekbKFBq26k6KywwHUNCqFGvlu4zKRwpEihQaVfpOissLdVoUOp9qpB
ptO7UKSnvc2hQatupOikaLFmoUG8xqilpWnFR6FGuWqrrqbTxaUNCqjku3mhQaVf
pOi0wrOloUGy16Tpw/ikwKFDptOrbqxWp2ShQrZWoUGlX8L4pmm4uKFBrNKms7Jg
ufqhQaSjpWkNCqjjvdehQ6jkwtWloru0t0yqzKFBq2irbqRIpUi/+qywskOhQaVI
pdussK5noUGlSL3irLC4cqFBpUisT6ywr+cNCqFGpV+kSKVIsWassKanoUGlSKZw
rLC+p6FBpUi1taywqW6hQaVIrKKssKqtoUOmcKa5pKeo0qFBqOKloqzGpmgNCqFD
pty/86R3qNOhQbDfqKOxWqRsrPmhQrFawqSo+8u7oUGn9a+qpK+hQqf1vaulU6fM
oUG74ajGqKW1/KFBpNYNCqywpMGlv6FDp/Wpdbhgtduhba21w/2oTbrDoW6hQa7J
prO/+aWioUa2p6XwpKezeaFtpMHD/aFuoUGu7aywsqgNCrOloUOnXq5hqOCka6FB
wfamYqvEuFihQatLuqW3/qW/pKehRqRAqKWzX7TAoUGlSKywpHa4b6hvoUOkqqyw
q34NCqqroUGlvKbSrtGwT6rMoUGko7Sxu7OmV6FBpryx5KnSqr6kXaFDDQogICAg
pWqktailu3mhQa7Jq1Wko6ZQoUa12616pKekSKFBt6GhQq5MplWyp6FDoW27Yb5l
sFa1/qFuoUGkz977DQqssKhqveahQaTPq72ssKnzqMShRqFtvtSw6rWmoW6ttaZG
rLCnS6FBoW2/cKTRpGy2x6FurbW/z6ywtqGhRqFtDQq7oaTloW6ttbGurLC0xqFB
xaql16ywsnKhRqFtpnKqTKFurbWs3aywpGalzKTPoUGttab5rLCor6FGoW3D/baw
DQqhbqVIpqihQqS0oUKnu6FCtW6mWKaoqOLD/aFBrLChQqlfoUKvcaFCpdukwKdA
pXyzuaFGp/W1bqFtwW7D/qFuDQqlSKh0rbWs/aFBvEKp96l2oW2pUKl4rbWhbsWq
rbytWanToUamuajSrMa8c6FBpbK2t6bSrtWhQ6tlpUCkz7t5DQqhQaRTpmiko6TB
oUGufaVQpcGhbaTyuNattaFupM/GSqywpmK7uKFBoW2lqrbHrbWhbqTB3dyssK57
vXShQaSjDQqlaajMq0ihQaXnrLCys6hvoUOktaSnvsekaKFBu3ml56Sjpb+hRqVq
v1em86RIoUGlssCzwEio5LCwu/elR6FIDQqhbbNxq1Wk5aFupOqhR6F1pEqrx6hE
pOq3aqFDoXakz6ywpVOrSqFDtU2raKVTt+2ttanSumGkz6FDpLWlX6tVDQqzcabm
prmttaFBpeelart5pKeko6Vppc6qzKFD8ELqZKFBvnykSMRfpcmhQbftrbWnRbfQ
oUGmv6turNKttcO/DQqrzKSnw7+hQ6fBpHO37a21rLCpX6FBpr+rbqzSqUmssK+r
rOmkp6V1oUOmv7Ous7SoU6FBprmttbNRqfPD9qSkDQqhQaSjqr6kR6rMpvOp0qnT
rtehQ6VIp16yTL7HoUGlvKSnq2W7RKRdoUMNCiAgICClX6RIpKettaFBpmilSMF8
oUKy96ywr3ihRrDfp/WpdbhgpKqhR6F1u/Su2aS9u1C63qXyqfOleKRXv9ENCqXv
svehQapGs6Kk+rHmqKOu2aS9pGa2fabTpKOzrKFBrEeqvqnSqKWqzLL3pF2hQ7VN
q2iy96FCr3ilsqSjplANCqlJoUOhdqa5rLCqvq21qG+hQw0KICAgIKTSqqvF6abb
prO66/nSoUG66/nSv9ekp6ZutGOhRqRIpN+ms6nSpWio+qFBpWio+r/XpKembrRj
oUOmuQ0KrbWoo6nzuK+seKFCrn3C5KFDptOqZaVfvsekaMWqoW2pfK7RoW62s6Zu
pc20Y7H+oUOsT6ywpEC916qrxemhQQ0KpEC0TqRIsaGhQa7tpKOzcahvoUMNCiAg
ICCoaqrMoUGoa6RspKes/LrZoUGlaq7Rpmiwsq3JrLCk96RsoUalX6RIuUW1TKRA
pEipSaywqGqqzKFBpecNCqnSpbyz66FDsN+63qXyoUKtU7xXpKe4uaFBtreozKZy
xaqm1aFDDQogICAgrtehR73RpnKu0aFBsmqqzLO+plehQanOpKq7ebX8oUGs0q21
qfO3XqTPoUOm27ivrHihba1upc6mcq1iDQqhbqTAsmqmcq21sFahR61ZsFam87BW
pnehQbftrbWp87depM+hQaF1qfOyarNwu7uhdqFBoXWp87JqucWryKF2DQqhQaF1
smqlzqbwoXahQaF1smqxb6SvoXakp8P+rE+kXaFGrVmwZaV5pM6nVbX8oUG37a21
qG+3XqTPoUGhdaxHDQq62cBzsmqhdqFBoXWsR7rZpuWyaqF2oUGhdaazpcGkSLJq
oXahQaF1prOqwL1esmqhdqFBoXWmq6lssmq6uKF2DQqhQaF1rsqhQr5HsmqozKF2
pKfD/qxPpF2hQ6a/q26m3KS1puamuaTAp0+hQaxMtU2p9r7loUam06plpV+yVqZQ
DQqkQK21oUHB9qjMpWrFqqFBpKOlaabmqfOktaRdoUMNCiAgICCouKrMoUGlvKl3
pKe1/KFDoW2lqrbHoW6k6qFHoXWko6q+pNGkp7HzvnyouKFIp+2+fKdnprO4b6nz
sK0NCq+rqLihSKF2oW2y+KRsoW6kqqFHoXWk0ai4oUimYai4oUihdqFtun6u0aFu
pKqhR6F1rE+ouKFIq0SouKFIoXYNCqSnw/6sT6RdoUOm06VfpEinWalJrLCkXaFB
peessLt+qG+hQ8P4qsyk6qFHoXWhccO0w+OhcqSqoUehebCuqVsNCqFBoW2p9qFu
pKeq+aThqLihSKF6prmkU6ywpbypd8PjpUehSKF2taqk6qFHoXWm86ywpKO6uKFJ
pFel/bzQsN0NCqFBpFWk6KZDvHelSKfppKem1aFDoXYNCiAgICCmv6tuvsekaMWq
oW2lqrbHoW6hQaRmrNu2x616oUGm26ywpFqo0qFBrXim27HRpOqx0aFBpbSvfaRI
rXgNCqTqsdGhQ73RsE+2x6W8qKO4ybHRpM+hQa59pVClwcWqoW2lqrbHoW6hQbDf
pECzQqazprmttaFBpFOko6ilptsNCrHRoUKx0aRIpKenT6FBprmssKzvxnem1aFD
DQogICAgpWqkSKSqoUehdbtJuGTD+L7joUOhdqVIqOSssMW6sPim26isoUGko6/g
p0rAeaRdoUOnXqijpP2rSqV+DQqxraFBu3mmaKSjpb+hQaXnpdGkuqxWveKrT7PF
oUGlfrVMqH2udqTNrEem1aFDseelQKazpECrSqFBucG576S4DQqr0ra866ahQabb
s6+hdcOotnehdqFBpESmqKF18auscaF2oUGkuKvStaqkp6SqoUehdfGrsqeyRK23
oUGscatEDQqkeqTsoUOhdr/XoXWwcqZ7oXassKF1pcOme6F2oUGkuKvSsdKz+MKy
pOWhQcKypOWkqqFHoXmpsKiwp2SkSqFBDQq5RaaopXHB9aFDoXamcKa5pKfD/qFB
wXykZqzStU2hQ6S4q9Kk4rHQvdGkbKjNxaqhQaVIprmssLt8oUMNCiAgICCqZaVf
pMGn8KZyrLClatp6oUG7UKR1oUKkvaFCpVykVKZypKOmUKFBru2ssLv3pF2hQ6Tx
pUCms6RIplcNCsXeoUGm27rZrLDF1qFGple1YqFBptu62ayws0+hRqZXrKmhQabb
utmssKhMoUamVyAgoUGm27rZrLDmfKFDq0QNCrDfrbXD/abfv/mhQaXnqM+o5Kjg
rl3B17/Qr8mvxqhvoUMNCg0KwvjDwLLEpFGkRQ0KDQogICAgr3Wv867RuPGhQbdM
xb2vZLdOoUOmv6tuv86kqqFHoXWk2MN8rtGyqKFBpGSova2xpdikXaFDoXap067K
DQqhQqe6vmyrVaFBrNu7UKjGpKehQaxHtUy5ea9Ur1aqzKFDp16lrqnTqvm3fqFB
pVupyrdSrauhQanSqKOqa67RDQql56ZooUGm0+b2st+lXKTSu+Gm3KFBuUWko6/g
qM6qzKFBqH2l0bVMpMCsR6RdoUO1TabTprnDwKSjtre5TLrrDQqhQ6TSpamqzLPS
ptO0vKrMvH6hQbFgrLCkSKnSp9Coz6FBp/PEsaywstahRq2zpfKxTr/yp9mhQbJg
prOlSKRdDQqhQw0KICAgIKT9tmik1q23rHmkfqRooUG/vbSyplekSKFBwXylQLGp
qr6o5K7RoUHCvaVIr+Cm272qpF2hQ7+9pGy2sw0KqEO826TqoUehdadetduhbbv0
rtGhbqFBsMemqKRAqOWhQaTls7mlsLhxoUGm27/XpWnGW6FGsN+lSLWnuPGxbw0K
plehQaXnsqeoxqRdoUOhdqT9vcemYatgsk212KFBpH6+x8B1sdOhQavhwfakSsP2
oUGl57NRwqe5SqFDtVOlSA0KrtGkdaFBsVS58rhPutSkp7ahoUGor61Xtae1eKSn
p9ChQbnBrqyr66TqoUehdbCyqM+nXqSjqr6u0aFBpWmkow0KptyktaTpqLihSKF2
pUimucZbpKehQbdWpMWlSK7RptupUqFDwfa1TaFBvHK1VKSnpEihQaVIr+Cu0ane
wMKqzA0Kpmiob6FDrEe5RKSjplChQaSjrNussL/RpF2hQw0KICAgILHnpPOvtbvV
tLK2aKVIqNOhQadeqKOkR6T9r3Wv86ZoqG+hQa5hpKS5wbFvpFGo96FGpOiqvrOz
wfSpfg0KoUKov6XmpnuhQr+9sr2wc73RrtGhQbL2pKOxb7+qpKekp8XpoUGsR6xP
rtGkp7JXt72hQ7+9sd+4YKnSxdyhQQ0KpESla614pn6k1q7JqmukXaFDDQogICAg
rsqhQqe6pUio06FBpmiv4K7RqsyhQ6xHqOSuyatVoUG7vKzbrFapfKFBqdKms7Oh
zG+hQbeipb+lacZbDQqhQaSjtUyrVaZyoUGrRKywpGq3bKFDptyx56TRusqkp7ah
oUG0ta23pbzF3KFGpGqmUKSnpb2hQbNftMC0/qXNDQqhQ7+9pGy2s6fvqfamcsXp
oUGq8rOupP274abmsLCmcqFGtMKzpbW/tU2hQaVIrLC3oqahoUG1ZarqpKOmqKFB
DQqmaKnStsux0aFDptyssKRApnKhQbDfqKO8xsJJoUGpzqZrt3KwdaFBs3arS8Lg
sr6hQ7q4q+G8WMR5oUGypKSjDQqlaazdoUOlX7TCs+C2w6SnvmyhQa7RuPG7wK2u
oUGlW6VIsU27s7N5pnKhQbVUqeWsxqnzpr+rbqFDpESlSKbKDQqpwKywvH6hQail
pM+ssMXcoUGko6XOrLC9faFBsGyo06ywwmuhQafzpc2ssMSsoUGl/aRIrLCm0aFB
pnCmuatEDQqkQKFBuU26obhntsehQ7DfprOrwKS4vNCkdanzt6LB9aFBr2Sk36Rw
vsehQavhpc2udqSnqsyys6FDrK2p87v0DQqlvaFBr7Wu0cK1vGehQb3lqfOpuaTp
pmiob6FDDQogICAgpr+rbr5buMy2oaazoXG1Za7RveGhcqFBpESzs8H0qX6nzKRs
p/m5RKRoqdKssKFGqOSkSKW8rMbD0aZyDQqhQbu0rLCteatooUGmq6ZXtlGudqFB
pUCrVbbHq0ihQavhpc274aywqdK7fqRdoUMNCiAgICC1ZcO4pKekdaFBpeessKeu
qG+hRqbbpWqmV6RooUGmaKnOr+Ckp6FDp16uYbnBprOx56S4q9Kk4rVlws0NCrO2
pdW5zq6wpM6wqLnPoUGl58P4pM6kXaFDqlqvUKTTpGywvq/gvGevdaFBp6SkV7ur
q8ihQcBIqXnCSaxWoUENCqdZpqi8xqRIoUGlSLDdtaPAqaFBrNKqvqltpleob6FD
v722TqFCvEKntaX9oUK8QsZGoUGow6Tlvsekd6V+oUENCrRfqM6muaproUPm9r5c
pWqktaFBr1OlacRft1KhQ61ZqXilvLNxxeOhQahDs1GkvahwqM+lT6FBpeessLVU
p9ANCqFDp2S/pMVVpGi63aVYqK208KpGpP2w6qjNraahQavhrLDC7atuqbKmRLq7
sNGteKFBprOkbKTqrnihQabotMINCqSkrtGxy6RIoUGk96RsqMOms7VertGkp8PA
oUGk16eupKarQ6FBsWCzUaS4q9Kp0qjPoUGoQ8Nostur66FDtF4NCquwvEKpqKFB
6dGkp6RspF2hQaVLrLDFWcNNqbK63rBPoUKlraTzv6SlT6FBpH6+x6fWpGihQabT
tWW1tK3boUMNCqvhwEiqWrOupP2kSri+oUGkVahjpKex0aFBuUWssLOwxUCteLVl
pOSmv6Z4vsChQbtQvdGkdaWpwvizQqFDplYNCqjPpFS95bOjpKO+5bVloUGqvblC
r8C3fqFBsFqoo6a5rqKlR6FIDQogICAgqbel2qSnp1GhQaVIq8Kk0aRVoUGl/aT9
qdKlSMZbvHe+3L3loUGl58DZqK2kp6vmsMikXaFDpr+rbr/XDQqlQKSnsWCuZ6FB
pUissKdMrmehQathsMO+p6XNoUGmaKSjst+muaFGp0+ms7PVrmehQa56pH2q+L1i
oUGsSanzDQqt46q6oUG0pcX9pMmtsKFBpUim5sKnsmqhQ6i+v22xRsP4oUGkRrVM
qdKvcaFDtsPC96Snq+GhQaa5s065RaRgDQqhQ6plpV+k5aRooUGydr7lp0yuZ6FB
q0SqvbivrHikQL1ioUGkd7jRsGynTKFBpFSkRfhAtrChQbFg7d26Yb3nDQqhQ8H2
tU2tbru0uFahQbpJrL7DfqFBpKPEQKa8vfqssKSnoUMNCiAgICCkUrhiqsyhQbh0
pEikp7d+pF2hRqb9qvGlQLVMtF+ozq52oUGmaKSjr+CkpKFDpWqqzKFBpFKlSKhN
usMNCqFBpLWkSKXNusOp86RSoUam86rMoUimdblEq0i/0aFBsf2m5qRAqMahQaRS
sW+0Y6j2oUGkz6VPDQqhQaa5pKe/16VHoUmlQqRRpKSku6RDoUGlSKywpFek4qFB
ssqqvqRqt06hQaRTpKOpZaaxoUOkWq5nqV+wuKFBDQqm27VNpWKmrKFBpvOorL/g
pF2hQ6VAtsekqqFHoXW40bOxtqeqzKFBrLCwranStvqhQaei6UmzaL1hoUGmaKSj
DQq62a71oUOhdqdexluq8aVqpUio06FBpNe666euqsyhQbDfqMqp0KFCut7geKFC
s6K/XKbVoUGs0rVMqXim7KFBDQqmaKnOv6moYaFBprmopaVPpEivcatIoUPFbK3I
pUC69MRZsUuhQbFqrXSmuaZXoUGrS6az4Ee7fqFBpee617e9DQqkXaFDpM6sUKTl
rbeu8KFBsnako7PSrLCkp6FDp165wb7HoW2ku6TQpqGhbqFBpeetyKVAtqKmbqZL
oUG7RbFvDQqhbcBzrbqhbqFCoW2q97m8oW6hQqFtpcncRsXcoW6hQqFtpcm++qFu
pFGzXLrYrtGhQbBRqES1TMXnoUG0TaXnDQqurL19oUOkWrOxtqekp7NOoUG7UKTR
pmGt0aXNoUGl56ZOpfu8d6ZEoUGko6VppKOrSKFGpv2laLh0rEq7t6FBDQqlQLbH
s06u0aFBrNKlWKx5q1WhQailw+O7wLJMoUHF56TWpmumaKFDptymcKTPpOSko6bm
oUGzuqVIuUquYKFGDQrCa6fSsUixSqFBpKOnS6X7stehR6nrptOmaKfSoUGl57VM
r3GkXaFDDQogICAguuKzTqXnrE+ku8PArW6oxqFGptular6npGi916TRuUShQal3
q9+++qrMoUGs0r7Hs3Gkp6FDtU2laaVIDQqt3an6oUGko6VppUixTbd+oUOmv6tu
prm+x67tpNahQbDfrVO2p6+qrs+666SnoUGm7Kbcq26xZKTTpnWhQ6plDQqlX6Zo
vuWmubNOoUMNCiAgICDC5aTopKeoxqFBqPqnrrelw/ihQaSjxFWmvLHkpUim26lS
pF2hQ7dMuNHDxKnKoUGkcKRwqU2mWKFBqX4NCq5hsW+lSLHPq+ahQaXnrLCz06jG
oUGs06hqwcShQq7vpfKz9KtoqOSkSKRdoUMNCiAgICChbcKnoW6k6qFHoXWnZ6Rs
tUysR6Sjuf21XrfmoUOhdqVqqNOmV6RooUGmaKnSt1KmbqFDrK2p87HnquwNCqFB
puerYaRsrl2hQaSjqr61XqrMoUG4uaazqdLC9qFGpGqmUKVIpb2hQbS1rbe5ebrJ
oUO1TabTprm81tj+2P4NCrauvW+hQaazsmCo/at2oUmktaVAprG40aFBwfbF3Knz
pWqhQbVTqKylSLpar6uxoaRdoUOw36SjpWmlT6azutkNCsVBoUGoo6fQvrG2UaFB
s0Kkp6RVp6ShQaVIqPq03apNp06qpaSnsGShQ8C5pne5RLVTvkSkp6FBqnC6uLHk
pUcNCqFJDQogICAgoW2uYbt5oW6k6qFHoXWnZ6RspKOz1aFBrLCo5K3dpua0Y7lE
rEekXaFDoXahbb3Xu3mhbqSqoUehdaSjDQqms7PVq9mqzKVHoUissKSnoUG1U73l
pUekd6FDoXa1TatouHSkSKSjpc6z1avZrLCx0KFGpv2lSL7Hqsyko6VpDQqxYLrr
oUGms67Jr2itwqFBq2jFbKywpKehQbVTs9O5oa25qfy6zqFBpGG1Tbrdp6Sm1aFD
ptymcKdkpNOkbKVIDQqssLVMr3GhQalSrbOsTL3XpKehRqT9tcKhQrivrHihQrOz
qNSkp657oUGko7NcpdjGW6TisPWhQaa5qMO21L93DQqkp6fTpF2hQ6/gurissKjO
oUOlaqywpGqz1atopLu65qFBpHCz1atopEcgIKFBpLW1TL7lqsyhQ6TxpUCp0qbm
DQqhQaRAICCkUaRHICChXbTRoV6hQbzGs06yTLV1oUGko6ispWnm9qFDs/IgIKFd
tNGhXqazpOK9zaFCp6TB9KSnDQql2KFBu+GssLauwLihRqb9pU+kSK/U5aahQbxv
s+C56qZooUGko6VpsWCkXaFDDQogICAgp+uz/aSnwqehQarxpUC3VbrroUOlaqrM
oUG56qVIpHCop6FBrLCo5KXapKfFRKRdoUOktatosN+x/ajkDQrFu6FBr3GmaK9x
s9+hQaREprOtyqzxoUKxYbxDoUKvVLP9oUKwXKfAoULAc626pKemV6FDqOSk16eu
qsyhQaazDQq9rKrhxbuhQ6a8q26pUCAgoUGlsKW/pKekbKFBt3y9XbZQwLKhQbZQ
rbKkp6RsoUGow6/gpEC9YqV8pFG+bMW7DQqhQ7ZQpFO5waywpHC72aFBuG2z/ajk
pX6hQblqu9mn66SnoUG1TKnSpaKkXaFDpty/86VIqNOhQaXnqKO8c7nnDQqhQsT1
s6690aT9oUGms6a5rtWo46FBwXyw6rlFtUyn67FvpEDFu6rMoUO8dbTRpeeq8aVA
tq7AuKFBrvi3VMTADQrlpqFBrsmlaaywpKehQw0KDQqy16jussSkR6RRDQoNCiAg
ICCmuqrMoUGkSKSnsWCkwKFBpKOlaadLpF2hQ6depn6kUaRFoUGtyLHnrmGz4LbD
oUGo5Lahu1Cl1aRirLANCqXuqsyhQaXnsWC8xr36oUapr6nTvmy61qFBsW+m3Knz
pLWhQ6VqpEikqqFHoXWkraRRpKOssKTUoUOhdqdepHcNCqS7pFG+bKFBrEek36la
tU2hQaSjpUi03aZ+rLCpwKFDpf2ms623rvCkp69loUGxYLrDqWG1TaFBsuGu0a/A
w2gNCqFBpUissKa8u3yhQw0KICAgIKX9p2el/aTSpEis0qW8wdmr2L/zwsKkc6FB
rsi4rqa/s66qRrOioUOp07h0pb2hQaR3sdKoRLSts6OhQQ0Ksf3A575Fre2hQ7tY
tkC957vIpsqo4qFBpHep87StpnukcK2lpV+mYb9OICChXb9qoV6hQatLrcilu7TC
sl+oUw0KoUGsecL3pnCmuaFBvMakUaZ+tqGhQbW0qfPB2bHmoUOktcH2slakQKFB
rmG5RMFqvWGhQabzpdG/7Ka5qV7A5w0KuOq2T6FIpUK0rbOjpsO3tKFBtUy0X6Rt
v/KhQcHZs1GkVcDjoUGlvKywsW+tcKFDptupU6bbs2ShQbNlpN+o6A0KxeihQ61w
p16lU6fMoUGko7ftpUu2aaFGpv2lSKr5sEmhQbCpptez5q56oUGkrapBpKekuqFB
s8S1TKRApEihQQ0KvL22VqVMtm2hQbVMtF+46r2uoUaoz6a8taWoSLJfvHKn0KFB
pUissKX9pUCkp66ioUasR8BTq1+kSLahoUGkow0KtLG8WaWioUOt3aVIpV+k6KxG
sdDEWaTBoUGl/rVMwfSwaKrMrEekXaFDDQogICAgpLWmfqbRr2WrSaFBxWy1Talh
qb+hQbBaqESzxsKnpUehSKRApOmp8cF1oUGoTq9EptOkd6FBpKOz0rRfDQq+eqFB
wNSlSLFgpuehQ6X9pNKkSLHzrUmkp67JoUHE3aVAr+7ERKFBrmG27qrFraKhQaVT
p8ylrq56oUG0w765DQqydsGhoUHCw6S6tUwgIKFdv2qhXqFDp1637apRtMOkR6Rv
oUGm57RVpHelfqFBpECko7FvptvASKFBp8mkV7DfDQqsSaRDrFCqT6FGptymcMT6
qbik+qFCpcmzYqFCv/ykSKSnxN2hQajDtrewsazZoUHCs/J3qfq+uaFBrEeko7Fv
DQrA56FBuE+7eNl+2X2hQcCxpmKopaV+oUO4/KVIxb6l0qiuoUHFqKRnptOkVaFB
pa2mYbVMvFihRq1ZxN+r9LG9DQqko6q+pfyw7KFBt+2/dqRAsPSnQ8DwqfOlqqVr
q2Wr4aFBwEissKhwsE+m1aFDxka64aTFs12qRaRMoUGu0rHmDQqyu+2voUGw36RV
pdW1sLJNpPSwrrTHoUGko7FvprOwc6bXu+aqR6Snsr2hQ7/LpM2o0+xd5HSqzKFB
pECs0qnaDQqkp6FDpryx5K1ZuUinXqTfoUGms6Vbpf2nraFBq2iztKT3pKOntaFB
pmKmvKZ3pUehSKjkpLqo5aVcvHehQcBIDQqkT6nSptyhQaTFy9K63KXNuOqhQajP
reG+a6RdoUOlfK7Jsr2qwaFBqVChQqTVqdKx0KFBsf2kSKTFprqo5L/LDQqhQaSj
p9GntblEpF2hQ6hEvdGkuqjloUGraLVMr3GyaqFDsf6lzaywpKehQcK9vFe4b7LW
oUOtWbP4qsm3paSnDQq8d6FBwffFU6SntGShQaazrsnCTqjRoUGkzqRDpOulYqq7
xPWs1qFBseap86a8pF2hQw0KICAgIKTVpGykp7iuv8ukXaFBpKqhR6F1pWqqzKFB
udOm06SjvFihQ6VDqkam6KtupV+kp6RIpF2hQaSjpWmlSA0KpbHD0aRdoUOhdqnz
rE+ryqSnsVKlfKTYoUO1Tatop2ekbMCzpUCm5rlEoUGl56azpKOmdbxYudOkp67J
oUGqcA0KrLCoxrvaqdK5R6RdoUmnXqS1xfmuyKFBqK2tWa9CtrOhQbO6pbyqvqbz
tm2sT6deuK6mYaFGsN+37a7wtbSrSw0Krkmkp6bVoUOmvLHkqXmlSLbHt360raZX
rLCwyKFBpKOlacVVxcqmtMRboUGlSKj62KGoU6RdoUM=
Loading

0 comments on commit 51ed44e

Please sign in to comment.