1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.client5.http.entity.mime;
29
30 import java.io.ByteArrayOutputStream;
31 import java.io.IOException;
32 import java.io.OutputStream;
33 import java.nio.ByteBuffer;
34 import java.nio.CharBuffer;
35 import java.nio.charset.Charset;
36 import java.nio.charset.StandardCharsets;
37 import java.util.List;
38
39 import org.apache.hc.core5.util.Args;
40 import org.apache.hc.core5.util.ByteArrayBuffer;
41
42
43
44
45
46
47 abstract class AbstractMultipartFormat {
48
49
50
51
52 private String preamble;
53
54
55
56
57 private String epilogue;
58
59 static ByteArrayBuffer encode(
60 final Charset charset, final CharSequence string) {
61 final ByteBuffer encoded = charset.encode(CharBuffer.wrap(string));
62 final ByteArrayBuffer bab = new ByteArrayBuffer(encoded.remaining());
63 bab.append(encoded.array(), encoded.arrayOffset() + encoded.position(), encoded.remaining());
64 return bab;
65 }
66
67 static void writeBytes(
68 final ByteArrayBuffer b, final OutputStream out) throws IOException {
69 out.write(b.array(), 0, b.length());
70 }
71
72 static void writeBytes(
73 final CharSequence s, final Charset charset, final OutputStream out) throws IOException {
74 final ByteArrayBuffer b = encode(charset, s);
75 writeBytes(b, out);
76 }
77
78 static void writeBytes(
79 final CharSequence s, final OutputStream out) throws IOException {
80 final ByteArrayBuffer b = encode(StandardCharsets.ISO_8859_1, s);
81 writeBytes(b, out);
82 }
83
84 static boolean isLineBreak(final char ch) {
85 return ch == '\r' || ch == '\n' || ch == '\f' || ch == 11;
86 }
87
88 static CharSequence stripLineBreaks(final CharSequence s) {
89 if (s == null) {
90 return null;
91 }
92 boolean requiresRewrite = false;
93 int n = 0;
94 for (; n < s.length(); n++) {
95 final char ch = s.charAt(n);
96 if (isLineBreak(ch)) {
97 requiresRewrite = true;
98 break;
99 }
100 }
101 if (!requiresRewrite) {
102 return s;
103 }
104 final StringBuilder buf = new StringBuilder();
105 buf.append(s, 0, n);
106 for (; n < s.length(); n++) {
107 final char ch = s.charAt(n);
108 if (isLineBreak(ch)) {
109 buf.append(' ');
110 } else {
111 buf.append(ch);
112 }
113 }
114 return buf.toString();
115 }
116
117 static void writeField(
118 final MimeField field, final OutputStream out) throws IOException {
119 writeBytes(stripLineBreaks(field.getName()), out);
120 writeBytes(FIELD_SEP, out);
121 writeBytes(stripLineBreaks(field.getBody()), out);
122 writeBytes(CR_LF, out);
123 }
124
125 static void writeField(
126 final MimeField field, final Charset charset, final OutputStream out) throws IOException {
127 writeBytes(stripLineBreaks(field.getName()), charset, out);
128 writeBytes(FIELD_SEP, out);
129 writeBytes(stripLineBreaks(field.getBody()), charset, out);
130 writeBytes(CR_LF, out);
131 }
132
133 static final ByteArrayBuffer FIELD_SEP = encode(StandardCharsets.ISO_8859_1, ": ");
134 static final ByteArrayBuffer CR_LF = encode(StandardCharsets.ISO_8859_1, "\r\n");
135 static final ByteArrayBuffer TWO_HYPHENS = encode(StandardCharsets.ISO_8859_1, "--");
136
137 final Charset charset;
138 final String boundary;
139
140
141
142
143
144
145
146
147 public AbstractMultipartFormat(final Charset charset, final String boundary) {
148 super();
149 Args.notNull(boundary, "Multipart boundary");
150 this.charset = charset != null ? charset : StandardCharsets.ISO_8859_1;
151 this.boundary = boundary;
152 }
153
154
155
156
157
158
159
160
161
162
163
164
165 public AbstractMultipartFormat(final Charset charset, final String boundary, final String preamble, final String epilogue) {
166 super();
167 Args.notNull(boundary, "Multipart boundary");
168 this.charset = charset != null ? charset : StandardCharsets.ISO_8859_1;
169 this.boundary = boundary;
170 this.preamble = preamble;
171 this.epilogue = epilogue;
172 }
173
174 public AbstractMultipartFormat(final String boundary) {
175 this(null, boundary);
176 }
177
178 public abstract List<MultipartPart> getParts();
179
180
181
182
183
184
185
186
187
188
189
190
191
192 void doWriteTo(
193 final OutputStream out,
194 final boolean writeContent) throws IOException {
195
196 final ByteArrayBuffer boundaryEncoded = encode(this.charset, this.boundary);
197 if (this.preamble != null) {
198 writeBytes(this.preamble, out);
199 writeBytes(CR_LF, out);
200 }
201 for (final MultipartPart part : getParts()) {
202 writeBytes(TWO_HYPHENS, out);
203 writeBytes(boundaryEncoded, out);
204 writeBytes(CR_LF, out);
205
206 formatMultipartHeader(part, out);
207
208 writeBytes(CR_LF, out);
209
210 if (writeContent) {
211 part.getBody().writeTo(out);
212 }
213 writeBytes(CR_LF, out);
214 }
215 writeBytes(TWO_HYPHENS, out);
216 writeBytes(boundaryEncoded, out);
217 writeBytes(TWO_HYPHENS, out);
218 writeBytes(CR_LF, out);
219 if (this.epilogue != null) {
220 writeBytes(this.epilogue, out);
221 writeBytes(CR_LF, out);
222 }
223 }
224
225
226
227
228 protected abstract void formatMultipartHeader(
229 final MultipartPart part,
230 final OutputStream out) throws IOException;
231
232
233
234
235
236
237 public void writeTo(final OutputStream out) throws IOException {
238 doWriteTo(out, true);
239 }
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255 public long getTotalLength() {
256 long contentLen = 0;
257 for (final MultipartPart part : getParts()) {
258 final ContentBody body = part.getBody();
259 final long len = body.getContentLength();
260 if (len >= 0) {
261 contentLen += len;
262 } else {
263 return -1;
264 }
265 }
266 final ByteArrayOutputStream out = new ByteArrayOutputStream();
267 try {
268 doWriteTo(out, false);
269 final byte[] extra = out.toByteArray();
270 return contentLen + extra.length;
271 } catch (final IOException ex) {
272
273 return -1;
274 }
275 }
276
277 }