Skip to content

Commit 7c67c3a

Browse files
committed
Fix MultipartPart transfer, close AsyncHttpClient#1147
1 parent baf17bb commit 7c67c3a

File tree

4 files changed

+229
-23
lines changed

4 files changed

+229
-23
lines changed

client/src/main/java/org/asynchttpclient/request/body/multipart/part/MultipartPart.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ protected long transfer(ByteBuf source, ByteBuf target, MultipartState sourceFul
191191
state = sourceFullyWrittenState;
192192
return sourceRemaining;
193193
} else {
194-
target.writeBytes(source, targetRemaining - sourceRemaining);
194+
target.writeBytes(source, targetRemaining);
195195
return targetRemaining;
196196
}
197197
}

client/src/test/java/org/asynchttpclient/request/body/multipart/part/MultipartPartTest.java

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,29 @@
22

33
import static java.nio.charset.StandardCharsets.UTF_8;
44
import static org.testng.Assert.assertEquals;
5+
import io.netty.buffer.ByteBuf;
6+
import io.netty.buffer.ByteBufAllocator;
7+
import io.netty.handler.codec.http.DefaultHttpHeaders;
8+
import io.netty.handler.codec.http.HttpHeaders;
59

610
import java.io.IOException;
11+
import java.net.URISyntaxException;
712
import java.nio.channels.WritableByteChannel;
813
import java.nio.charset.Charset;
14+
import java.nio.charset.StandardCharsets;
15+
import java.util.ArrayList;
16+
import java.util.List;
917

18+
import org.apache.commons.io.FileUtils;
1019
import org.asynchttpclient.request.body.multipart.FileLikePart;
20+
import org.asynchttpclient.request.body.multipart.MultipartBody;
21+
import org.asynchttpclient.request.body.multipart.MultipartUtils;
22+
import org.asynchttpclient.request.body.multipart.Part;
23+
import org.asynchttpclient.request.body.multipart.StringPart;
1124
import org.asynchttpclient.request.body.multipart.part.PartVisitor.CounterPartVisitor;
25+
import org.asynchttpclient.test.TestUtils;
1226
import org.testng.annotations.Test;
1327

14-
import io.netty.buffer.ByteBuf;
15-
1628
public class MultipartPartTest {
1729

1830
@Test
@@ -21,8 +33,7 @@ public void testVisitStart() {
2133
try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, new byte[10])) {
2234
CounterPartVisitor counterVisitor = new CounterPartVisitor();
2335
multipartPart.visitStart(counterVisitor);
24-
assertEquals(counterVisitor.getCount(), 12,
25-
"CounterPartVisitor count for visitStart should match EXTRA_BYTES count plus boundary bytes count");
36+
assertEquals(counterVisitor.getCount(), 12, "CounterPartVisitor count for visitStart should match EXTRA_BYTES count plus boundary bytes count");
2637
}
2738
}
2839

@@ -32,8 +43,7 @@ public void testVisitStartZeroSizedByteArray() {
3243
try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, new byte[0])) {
3344
CounterPartVisitor counterVisitor = new CounterPartVisitor();
3445
multipartPart.visitStart(counterVisitor);
35-
assertEquals(counterVisitor.getCount(), 2,
36-
"CounterPartVisitor count for visitStart should match EXTRA_BYTES count when boundary byte array is of size zero");
46+
assertEquals(counterVisitor.getCount(), 2, "CounterPartVisitor count for visitStart should match EXTRA_BYTES count when boundary byte array is of size zero");
3747
}
3848
}
3949

@@ -54,10 +64,8 @@ public void testVisitDispositionHeaderWithFileName() {
5464
try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, new byte[0])) {
5565
CounterPartVisitor counterVisitor = new CounterPartVisitor();
5666
multipartPart.visitDispositionHeader(counterVisitor);
57-
assertEquals(counterVisitor.getCount(), 68,
58-
"CounterPartVisitor count for visitDispositionHeader should be equal to "
59-
+ "CRLF_BYTES length + CONTENT_DISPOSITION_BYTES length + part name length + file name length when"
60-
+ " both part name and file name are present");
67+
assertEquals(counterVisitor.getCount(), 68, "CounterPartVisitor count for visitDispositionHeader should be equal to "
68+
+ "CRLF_BYTES length + CONTENT_DISPOSITION_BYTES length + part name length + file name length when" + " both part name and file name are present");
6169
}
6270
}
6371

@@ -123,9 +131,8 @@ public void testVisitCustomHeadersWhenNoCustomHeaders() {
123131
try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, new byte[0])) {
124132
CounterPartVisitor counterVisitor = new CounterPartVisitor();
125133
multipartPart.visitCustomHeaders(counterVisitor);
126-
assertEquals(counterVisitor.getCount(), 0,
127-
"CounterPartVisitor count for visitCustomHeaders should be zero for visitCustomHeaders "
128-
+ "when there are no custom headers");
134+
assertEquals(counterVisitor.getCount(), 0, "CounterPartVisitor count for visitCustomHeaders should be zero for visitCustomHeaders "
135+
+ "when there are no custom headers");
129136
}
130137
}
131138

@@ -136,8 +143,7 @@ public void testVisitCustomHeaders() {
136143
try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, new byte[0])) {
137144
CounterPartVisitor counterVisitor = new CounterPartVisitor();
138145
multipartPart.visitCustomHeaders(counterVisitor);
139-
assertEquals(counterVisitor.getCount(), 27,
140-
"CounterPartVisitor count for visitCustomHeaders should include the length of the custom headers");
146+
assertEquals(counterVisitor.getCount(), 27, "CounterPartVisitor count for visitCustomHeaders should include the length of the custom headers");
141147
}
142148
}
143149

@@ -153,14 +159,12 @@ public void testVisitEndOfHeaders() {
153159

154160
@Test
155161
public void testVisitPreContent() {
156-
TestFileLikePart fileLikePart = new TestFileLikePart("Name", "application/test", UTF_8, "contentId", "transferEncoding",
157-
"fileName");
162+
TestFileLikePart fileLikePart = new TestFileLikePart("Name", "application/test", UTF_8, "contentId", "transferEncoding", "fileName");
158163
fileLikePart.addCustomHeader("custom-header", "header-value");
159164
try (TestMultipartPart multipartPart = new TestMultipartPart(fileLikePart, new byte[0])) {
160165
CounterPartVisitor counterVisitor = new CounterPartVisitor();
161166
multipartPart.visitPreContent(counterVisitor);
162-
assertEquals(counterVisitor.getCount(), 214, "CounterPartVisitor count for visitPreContent should "
163-
+ "be equal to the sum of the lengths of precontent");
167+
assertEquals(counterVisitor.getCount(), 214, "CounterPartVisitor count for visitPreContent should " + "be equal to the sum of the lengths of precontent");
164168
}
165169
}
166170

@@ -174,6 +178,38 @@ public void testVisitPostContents() {
174178
}
175179
}
176180

181+
@Test
182+
public void transferToShouldWriteStringPart() throws IOException, URISyntaxException {
183+
String text = FileUtils.readFileToString(TestUtils.resourceAsFile("test_sample_message.eml"));
184+
185+
List<Part> parts = new ArrayList<>();
186+
parts.add(new StringPart("test_sample_message.eml", text));
187+
188+
HttpHeaders headers = new DefaultHttpHeaders();
189+
headers.set(
190+
"Cookie",
191+
"open-xchange-public-session-d41d8cd98f00b204e9800998ecf8427e=bfb98150b24f42bd844fc9ef2a9eaad5; open-xchange-secret-TSlq4Cm4nCBnDpBL1Px2A=9a49b76083e34c5ba2ef5c47362313fd; JSESSIONID=6883138728830405130.OX2");
192+
headers.set("Content-Length", "9241");
193+
headers.set("Content-Type", "multipart/form-data; boundary=5gigAKQyqDCVdlZ1fCkeLlEDDauTNoOOEhRnFg");
194+
headers.set("Host", "appsuite.qa.open-xchange.com");
195+
headers.set("Accept", "*/*");
196+
197+
String boundary = "uwyqQolZaSmme019O2kFKvAeHoC14Npp";
198+
199+
List<MultipartPart<? extends Part>> multipartParts = MultipartUtils.generateMultipartParts(parts, boundary.getBytes());
200+
MultipartBody multipartBody = new MultipartBody(multipartParts, "multipart/form-data; boundary=" + boundary, boundary.getBytes());
201+
202+
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(8 * 1024);
203+
204+
try {
205+
multipartBody.transferTo(byteBuf);
206+
byteBuf.toString(StandardCharsets.UTF_8);
207+
} finally {
208+
multipartBody.close();
209+
byteBuf.release();
210+
}
211+
}
212+
177213
/**
178214
* Concrete implementation of {@link FileLikePart} for use in unit tests
179215
*
@@ -200,8 +236,7 @@ public TestFileLikePart(String name, String contentType, Charset charset, String
200236
this(name, contentType, charset, contentId, transfertEncoding, null);
201237
}
202238

203-
public TestFileLikePart(String name, String contentType, Charset charset, String contentId, String transfertEncoding,
204-
String fileName) {
239+
public TestFileLikePart(String name, String contentType, Charset charset, String contentId, String transfertEncoding, String fileName) {
205240
super(name, contentType, charset, fileName, contentId, transfertEncoding);
206241
}
207242
}

client/src/test/java/org/asynchttpclient/test/TestUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public static synchronized int findFreePort() throws IOException {
105105
}
106106
}
107107

108-
private static File resourceAsFile(String path) throws URISyntaxException, IOException {
108+
public static File resourceAsFile(String path) throws URISyntaxException, IOException {
109109
ClassLoader cl = TestUtils.class.getClassLoader();
110110
URI uri = cl.getResource(path).toURI();
111111
if (uri.isAbsolute() && !uri.isOpaque()) {
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
Return-Path: <${OX_USER_EMAIL1}>
2+
To: ${OX_USER_FIRST_NAME} ${OX_USER_LAST_NAME} <${OX_USER_EMAIL1}>
3+
Subject: Testing ${OX_USER_FIRST_NAME} ${OX_USER_LAST_NAME}' MIME E-mail composing and sending PHP class: HTML message
4+
From: ${username} <${OX_USER_EMAIL1}>
5+
Reply-To: ${username} <${OX_USER_EMAIL1}>
6+
Sender: ${OX_USER_EMAIL1}
7+
X-Mailer: http://www.phpclasses.org/mimemessage $Revision: 1.63 $ (mail)
8+
MIME-Version: 1.0
9+
Content-Type: multipart/mixed; boundary="652b8c4dcb00cdcdda1e16af36781caf"
10+
Message-ID: <[email protected]>
11+
Date: Sat, 30 Apr 2005 19:28:29 -0300
12+
13+
14+
--652b8c4dcb00cdcdda1e16af36781caf
15+
Content-Type: multipart/related; boundary="6a82fb459dcaacd40ab3404529e808dc"
16+
17+
18+
--6a82fb459dcaacd40ab3404529e808dc
19+
Content-Type: multipart/alternative; boundary="69c1683a3ee16ef7cf16edd700694a2f"
20+
21+
22+
--69c1683a3ee16ef7cf16edd700694a2f
23+
Content-Type: text/plain; charset=ISO-8859-1
24+
Content-Transfer-Encoding: quoted-printable
25+
26+
This is an HTML message. Please use an HTML capable mail program to read
27+
this message.
28+
29+
--69c1683a3ee16ef7cf16edd700694a2f
30+
Content-Type: text/html; charset=ISO-8859-1
31+
Content-Transfer-Encoding: quoted-printable
32+
33+
<html>
34+
<head>
35+
<title>Testing Manuel Lemos' MIME E-mail composing and sending PHP class: H=
36+
TML message</title>
37+
<style type=3D"text/css"><!--
38+
body { color: black ; font-family: arial, helvetica, sans-serif ; backgroun=
39+
d-color: #A3C5CC }
40+
A:link, A:visited, A:active { text-decoration: underline }
41+
--></style>
42+
</head>
43+
<body>
44+
<table background=3D"cid:4c837ed463ad29c820668e835a270e8a.gif" width=3D"100=
45+
%">
46+
<tr>
47+
<td>
48+
<center><h1>Testing Manuel Lemos' MIME E-mail composing and sending PHP cla=
49+
ss: HTML message</h1></center>
50+
<hr>
51+
<P>Hello Manuel,<br><br>
52+
This message is just to let you know that the <a href=3D"http://www.phpclas=
53+
ses.org/mimemessage">MIME E-mail message composing and sending PHP class</a=
54+
> is working as expected.<br><br>
55+
<center><h2>Here is an image embedded in a message as a separate part:</h2>=
56+
</center>
57+
<center><img src=3D"cid:ae0357e57f04b8347f7621662cb63855.gif"></center>Than=
58+
k you,<br>
59+
mlemos</p>
60+
</td>
61+
</tr>
62+
</table>
63+
</body>
64+
</html>
65+
--69c1683a3ee16ef7cf16edd700694a2f--
66+
67+
--6a82fb459dcaacd40ab3404529e808dc
68+
Content-Type: image/gif; name="logo.gif"
69+
Content-Transfer-Encoding: base64
70+
Content-Disposition: inline; filename="logo.gif"
71+
Content-ID: <ae0357e57f04b8347f7621662cb63855.gif>
72+
73+
R0lGODlhlgAjAPMJAAAAAAAA/y8vLz8/P19fX19f339/f4+Pj4+Pz7+/v///////////////////
74+
/////yH5BAEAAAkALAAAAACWACMAQwT+MMlJq7046827/2AoHYChGAChAkBylgKgKClFyEl6xDMg
75+
qLFBj3C5uXKplVAxIOxkA8BhdFCpDlMK1urMTrZWbAV8tVS5YsxtxmZHBVOSCcW9zaXyNhslVcto
76+
RBp5NQYxLAYGLi8oSwoJBlE+BiSNj5E/PDQsmy4pAJWQLAKJY5+hXhZ2dDYldFWtNSFPiXssXnZR
77+
k5+1pjpBiDMJUXG/Jo7DI4eKfMSmxsJ9GAUB1NXW19jZ2tvc3d7f4OHi2AgZN5vom1kk6F7s6u/p
78+
m3Ab7AOIiCxOyZuBIv8AOeTJIaYQjiR/kKTr5GQNE3pYSjCJ9mUXClRUsLxaZGciC0X+OlpoOuQo
79+
ZKdNJnIoKfnxRUQh6FLG0iLxIoYnJd0JEKISJyAQDodp3EUDC48oDnUY7HFI3wEDRjzycQJVZCQT
80+
Ol7NK+G0qgtkAcOKHUu2rNmzYTVqRMt2bB49bHompSchqg6HcGeANSMxr8sEa2y2HexnSEUTuWri
81+
SSbkYh7BgGVAnhB1b2REibESYaRoBgqIMYx59tFM9AvQffVG49P5NMZkMlHKhJPJb0knmSKZ6kSX
82+
JtbeF3Am7ocok6c7cM7pU5xcXiJJETUz16qPrzEfaFgZpvzn7h86YV5r/1mxXeAUMVyEIpnVUGpN
83+
RlG2ka9b3lP3pm2l6u7P+l/YLj3+RlEHbz1C0kRxSITQaAcilVBMEzmkkEQO8oSOBNg9SN+AX6hV
84+
z1pjgJiAhwCRsY8ZIp6xj1ruqCgeGeKNGEZwLnIwzTg45qjjjjz2GEA5hAUp5JBEFmnkkSCoWEcZ
85+
X8yohZNK1pFGPQS4hx0qNSLJlk9wCQORYu5QiMd7bUzGVyNlRiOHSlpuKdGEItHQ3HZ18beRRyws
86+
YSY/waDTiHf/tWlWUBAJiMJ1/Z0XXU7N0FnREpKM4NChCgbyRDq9XYpOplaKopN9NMkDnBbG+UMC
87+
QwLWIeaiglES6AjGARcPHCWoVAiatcTnGTABZoLPaPG1phccPv366mEvWEFSLnj+2QaonECwcJt/
88+
e1Zw3lJvVMmftBdVNQS3UngLCA85YHIQOy6JO9N4eZW7KJwtOUZmGwOMWqejwVW6RQzaikRHX3yI
89+
osKhDAq8wmnKSmdMwNidSOof9ZG2DoV0RfTVmLFtGmNk+CoZna0HQnPHS3AhRbIeDpqmR09E0bsu
90+
soeaw994z+rwQVInvqLenBftYjLOVphLFHhV9qsnez8AEUbQRgO737AxChjmyANxuEFHSGi7hFCV
91+
4jxLst2N8sRJYU+SHiAKjlmCgz2IffbLI5aaQR71hnkxq1ZfHSfKata6YDCJDMAQwY7wOgzhjxgj
92+
VFQnKB5uX4mr9qJ79pann+VcfcSzsSCd2mw5scqRRvlQ6TgcUelYhu75iPE4JejrsJOFQAG01277
93+
7bjnrvvuvPfu++/ABy887hfc6OPxyCevPDdAVoDA89BHL/301Fdv/fXYZ6/99tx3Pz0FEQAAOw==
94+
95+
--6a82fb459dcaacd40ab3404529e808dc
96+
Content-Type: image/gif; name="background.gif"
97+
Content-Transfer-Encoding: base64
98+
Content-Disposition: inline; filename="background.gif"
99+
Content-ID: <4c837ed463ad29c820668e835a270e8a.gif>
100+
101+
R0lGODlh+wHCAPMAAKPFzKLEy6HDyqHCyaDByJ/Ax56/xp2+xZ28xJy7w5u6wpq5wZm4wJm3v5i2
102+
vpe1vSwAAAAA+wHCAEME/hDISau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqP
103+
yKRyyWw6n9CodEqtWq+gwSHReHgfjobY8X00FIc019tIHAYS7dqcQCDm3vC4fD4QAhUBBFsMZF8O
104+
hnkLCAYFW11tb1iTlJWWOXJdZZtmC24Eg3hgYntfbXainJ2fgBSZbG5wFAG0E6+RoAZ3CbwJCgya
105+
p3cMbAyevQcFAgMGCcRmxr1uyszOxQq+wF4MdcPFx7zJApfk5eYhr3SSGemRsu3dc+4iAqELhZwO
106+
0X6hkHUHCBRoGtUg0RkEAAUeKhhGAcICBQIODIPooIEBzCTmKcjGYSNd/go3VvQo65zJkyhTqlzJ
107+
sqXLlzBjypxJs6bNmzhz6tzJs6fPn0CDCh1KtKjRo0iTKl3KtKnTp1CXBhhAwECaq1gPNCIwANDU
108+
qmkMcG311apWULmyZt3alcPXAma1FgAlgCxVq2LbRt3LF0Y7hwWoEjLEDZUmff8AOjMkTB5gwYu3
109+
JbhIQUDEZw+4+aE1aNc0R2vcDYjoDBgpBoUDj95yzzRqbH7qgW4t5vUnAfVAoj7NwOOf1QloN7Ad
110+
u1Xf41b+IlCNsa6rR7DWwTPccTnG5sYvCEKwgPGiZI64A9OsK/Q/BM/0YfuFz13VOwsULLhHps+f
111+
98Hl0zeDRk0X9Qih/vLPWPjFN197aPyB3IJVBLDMdc5t4OB1A0QowYQQ0vIgdilgyGEgG1roYV0j
112+
GufhhyBSWGF2s2yIYosqWsjgjDTWaOONOOao44489ujjj0AGKeSQRBZp5JFIJqnkkkw26eSTUMJU
113+
llpYseXVXWGNdSGWZ6EVF5VWukUVXFdtRUCEU+bFYpRslqNcYKHgk1k8hxWWxjCM0VkdnINJRtkE
114+
lqH3hWZ/CKJYOBBBJxppu/FWh2qzNUrcmQRE6lpvt+UWUKPD9cbIb5bWhmlxbbL5JoUywiMddHRQ
115+
x591GWqwXXdsfJeoeMO5UZ4/AaaHKXv1xVKgfghuNuyB9fUHHYAA/u2CEIHlGbiffWuWyuSJMmKA
116+
bXbbbtuhi9kCUOIEJY57oYsraoduuOfGWO2J6Vor77z01mvvvfjmq+++/Pbr778AByzwwAQXbPDB
117+
CCfcZDobldLRVfLEEgerjQ1EEEemJMiioZEdkggYizSiqMQKl5wCw6qswg+rDTvc6h0Wq9KAJ5tV
118+
oGpJF9YysXn8lCfNL8HE88xw4EyzTDNDR4MMNUhfk40mhXkDTdHimHzjzRpgDcB0MEeHswf1sCZn
119+
GfrQDMrIAYZEkEEOJTQRQweBp5FIDTGCEUiHYWwRXHOPMpLdVgcu+OCEF2744YgnrvjijDfu+OOQ
120+
Ry755JRXbvnl/phnrvnmnHfu+eegZ57RAqSUzptv75E+M+Bb66L6InZwZ7rpr31aLQBhb2pap548
121+
e7TsIX8dOr/pIIZQQphFHfGqEbtq/J2/DDrZ13Ga0jt8h/XX9TxvfRmmuPVUatb34INCplxakjtm
122+
XOQ7aP74c+k1fE4MD7fefvxBbLEeLldsyq/4o9ZzHOOHylBFS7f4RJxQMx/8MeB4ggIDA02ziLno
123+
wlfGoOByKnUAhZQNWfkzwAXzMEExVFB+86NJ/TDVC4SIZRzFs5Ni5OQ/p7XwLOOwQDXSswgFiYuD
124+
Z4GMP8AjtvGgJk9aYU2davdCeyzRU2LpBwkb2KjvWCU4T/TN/u1S+BKtYUBrXFue8DYQKFoVAzXa
125+
eJh/XiYPpZEOFhAMTnzkk8aQWQU+c7yHJkIGkGd4SkDhMJ9i5qMAOu4RAWfiYk1yxwvfaYCRA8oh
126+
JF14x0bGhgSyaZY07JCMRDLyWWnxTOyc1UmweMaSL5zSKf/xQgnk5lA3TCWWVunCRCrylrjMpS53
127+
ycte+vKXwAymMIdJzGIa85jITKYyl8nMZjrzmdCMpjSnSc1qWvOa2MymvkY3u9IxMReyW92fuLm6
128+
2Kmum53SIgZyxx7e9C423AyeNnkUw8RsSnqumsfWKKYnCdozen6iHiGsF483gkF7PIND96oUP7KE
129+
73zteyj8/tK3JfGVqaHkkmhYMDrPJqzwfjRUlij4hzE4ds1pdGSMxgYYjAQZEBRtSeDKSmMMEGYG
130+
ghjU4+osGEF9ZNCEG3SEB2s6LTSIsKcl3CkKO2qEj24Sh/ucw/NmmCdXQQMbsbSlzZoGMkSSBYh5
131+
kWIkEhWc3aARiVc0qE+hSCklkvCbUpQgFTWYRCy+la1bZGoQvHgBMPIznyT7QBkNgsY05m+NNSQa
132+
Lwx6ijvJsZB69IIdB5nHOjKij9twCCAVGJ7HGlKyiMyhXo0wyUtmoLS2LK0ID+XIEWRys5ycyzg+
133+
yQ9TtjB2lpyLbZ8qy91mVZK+ReWZVCkNVmp1tMhNrnKX/svc5jr3udCNrnSnS93qWve62M2udrfL
134+
3e5697vgDa94x0ve8pr3vOhNr3rXy972uve98I2vfOdLXxrBS0Uv8lZGUaUh/OKXXRmAV7jMVV+X
135+
QLK4vD0TaoHLWq1UEsEJFu0FXknLh3iyM5EssEtQlrK98ZN5QbNqyl71pwqEza752MfZEqrhljg1
136+
pYMKkBh3FuKTXtUX+LupMkwcETNCA40D6QNiA3tfdunXAkdOEX+1Ba68tjiqLbVOnKp60oNAam6J
137+
fcyUvTYLAnDHOw8Jjx7Js71YTKWzxX1IV76iyayuWTCwDSIgKJxmqLI5zmp6sg5ZNdV7bkPGQWYh
138+
0EzR/s8+A1THEt6hIrx6IbByRawKHKjfpEfExVREpUEdzKX3dJe5UaQ6UdT0p18VGCfPF2X8S4QD
139+
QgaamI24hi1TtTxZyuVZ6AzK6gBnIbE66DmhImlzxAYouUq0XQ+oUhG039P+rAZgG7u1erYFyy6W
140+
Tt85ddkmHak3PWVaWuePAC9F4Mh6dgdjB/A8tCqbscUxWLmumxp8jsa5A5RuY7xbwtHGtT+Phz69
141+
nGo0WC60DPt9u0AljxWG8kylh9hsRKw1jbiwx24cDsUKSRwYFPdIq2347NoWkSEAKnG++brnGes7
142+
sYH1QPVqVdDsOZZXUlN2WYO1soCA9JBoScjNQdvs/n3fKXaxYefOH9BDfD+Z5Db78Dv+WuWUd4Bj
143+
YwPDx1bNiI03BoO7yRi9CzJBBLlQdj5tTbKIOFQqikHjruN6Bovlw5GnXZxjtMXbZ01O2NnhdawL
144+
ASOFw8BIxpOSuutUYWfmBjW0U1S+gczhqy0Wzuhmd7Ur5RYW/01Tz3dKcpYVl/Isrs2jBSyZJ4H7
145+
LIq+4VYUL2NZaCMgQiY1LXSjFH09wWexvovGvvawX2q+d8/73vv+98APvvCHT/ziG//4yE++8pfP
146+
/OY7//nQj770p0/96lv/+tjPvva3z/3ue//74A+/+MdP/vKb//zoT7/6e3Lf/3KryTDKUPvdBQIB
147+
/q+JwOuPwYEhbFzcYDjDuPN/lARL/FdLRlcZwdUNnTRbGAZt+fcCHCYzGqd0NJZtrsYJFjFGJ2ZQ
148+
m1A2kcZiD+gXLKNsMMZsTQdiFvg/IJUID7RjldFjhAVkGaM/6lASRfYu8KcuS6aDO4hkOfh7p7Jl
149+
bBRlVxYSWSZlfVKDXfZltRJmADFmulJmb3BmBJhbb9YZp1RLV9hmwtUWdBZhnYeFCaZ7Rxdv/5Q8
150+
gKaCvNBrQ0hCZxhjLhgHXEV1PiQIjhBEkDZT6VFSmkFWhbBppMZBljZqVtZpIUGIqCNqevMYlhdf
151+
qEYKslZ10zZibbgQDkN1IndyTkcLxiFTulZI/muYRsrjbKA4bNYwNR1nPsn2K6J4PKdYbKXYbSM3
152+
bSQVeWdybWwIa9Rmi0b3FwUEKAcUU+MGTr4AivP2hGSgbqDIbjDobssIb1IlbzSEbslob894gGUY
153+
jYkxeyf3GABnhAK3jeTDYxE0J5uRcEtjdYUnaoMXHStGGxlnNxs4cYgARRt3Y8UobB5XVhhXjyTR
154+
e0jnbfoURkGzDh+wcquACmqFUDD3iiw0LZFmczhmWTknkZ9FdK5IDH0GdArWGaB4kUXHewEpbSZH
155+
kLX2AVA3dVPHamgjNQ8XZG0Ddl2XLF9HOmF3RPmTKGV3IGdXdWl3k2zXiPBVd3nXV3PHOkRpgk5A
156+
lYlgg2F8Fw3WlnZW9HiCB2Q0Y3ic8k2Kl5V4JQhUiXgWFgqUh1e9h3mcpy2epxdm+XnjQ1EiMHoQ
157+
pVtogiWuV3urBxGod4Xnw41huJfjKHvtg3t8GYKEWZiGeZiImZiKuZiM2ZiO+ZiQGZmSOZmUWZmW
158+
eZmYmZmauZmc2ZlCEQEAOw==
159+
160+
--6a82fb459dcaacd40ab3404529e808dc--
161+
162+
--652b8c4dcb00cdcdda1e16af36781caf
163+
Content-Type: text/plain; name="attachment.txt"
164+
Content-Transfer-Encoding: base64
165+
Content-Disposition: attachment; filename="attachment.txt"
166+
167+
VGhpcyBpcyBqdXN0IGEgcGxhaW4gdGV4dCBhdHRhY2htZW50IGZpbGUgbmFtZWQgYXR0YWNobWVu
168+
dC50eHQgLg==
169+
170+
--652b8c4dcb00cdcdda1e16af36781caf--
171+

0 commit comments

Comments
 (0)