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 static ByteArrayBuffer encode(
50 final Charset charset, final CharSequence string) {
51 final ByteBuffer encoded = charset.encode(CharBuffer.wrap(string));
52 final ByteArrayBuffer bab = new ByteArrayBuffer(encoded.remaining());
53 bab.append(encoded.array(), encoded.arrayOffset() + encoded.position(), encoded.remaining());
54 return bab;
55 }
56
57 static void writeBytes(
58 final ByteArrayBuffer b, final OutputStream out) throws IOException {
59 out.write(b.array(), 0, b.length());
60 }
61
62 static void writeBytes(
63 final CharSequence s, final Charset charset, final OutputStream out) throws IOException {
64 final ByteArrayBuffer b = encode(charset, s);
65 writeBytes(b, out);
66 }
67
68 static void writeBytes(
69 final CharSequence s, final OutputStream out) throws IOException {
70 final ByteArrayBuffer b = encode(StandardCharsets.ISO_8859_1, s);
71 writeBytes(b, out);
72 }
73
74 static boolean isLineBreak(final char ch) {
75 return ch == '\r' || ch == '\n' || ch == '\f' || ch == 11;
76 }
77
78 static CharSequence stripLineBreaks(final CharSequence s) {
79 if (s == null) {
80 return null;
81 }
82 boolean requiresRewrite = false;
83 int n = 0;
84 for (; n < s.length(); n++) {
85 final char ch = s.charAt(n);
86 if (isLineBreak(ch)) {
87 requiresRewrite = true;
88 break;
89 }
90 }
91 if (!requiresRewrite) {
92 return s;
93 }
94 final StringBuilder buf = new StringBuilder();
95 buf.append(s, 0, n);
96 for (; n < s.length(); n++) {
97 final char ch = s.charAt(n);
98 if (isLineBreak(ch)) {
99 buf.append(' ');
100 } else {
101 buf.append(ch);
102 }
103 }
104 return buf.toString();
105 }
106
107 static void writeField(
108 final MimeField field, final OutputStream out) throws IOException {
109 writeBytes(stripLineBreaks(field.getName()), out);
110 writeBytes(FIELD_SEP, out);
111 writeBytes(stripLineBreaks(field.getBody()), out);
112 writeBytes(CR_LF, out);
113 }
114
115 static void writeField(
116 final MimeField field, final Charset charset, final OutputStream out) throws IOException {
117 writeBytes(stripLineBreaks(field.getName()), charset, out);
118 writeBytes(FIELD_SEP, out);
119 writeBytes(stripLineBreaks(field.getBody()), charset, out);
120 writeBytes(CR_LF, out);
121 }
122
123 static final ByteArrayBuffer FIELD_SEP = encode(StandardCharsets.ISO_8859_1, ": ");
124 static final ByteArrayBuffer CR_LF = encode(StandardCharsets.ISO_8859_1, "\r\n");
125 static final ByteArrayBuffer TWO_HYPHENS = encode(StandardCharsets.ISO_8859_1, "--");
126
127 final Charset charset;
128 final String boundary;
129
130
131
132
133
134
135
136
137 public AbstractMultipartFormat(final Charset charset, final String boundary) {
138 super();
139 Args.notNull(boundary, "Multipart boundary");
140 this.charset = charset != null ? charset : StandardCharsets.ISO_8859_1;
141 this.boundary = boundary;
142 }
143
144 public AbstractMultipartFormat(final String boundary) {
145 this(null, boundary);
146 }
147
148 public abstract List<MultipartPart> getParts();
149
150 void doWriteTo(
151 final OutputStream out,
152 final boolean writeContent) throws IOException {
153
154 final ByteArrayBuffer boundaryEncoded = encode(this.charset, this.boundary);
155 for (final MultipartPart part: getParts()) {
156 writeBytes(TWO_HYPHENS, out);
157 writeBytes(boundaryEncoded, out);
158 writeBytes(CR_LF, out);
159
160 formatMultipartHeader(part, out);
161
162 writeBytes(CR_LF, out);
163
164 if (writeContent) {
165 part.getBody().writeTo(out);
166 }
167 writeBytes(CR_LF, out);
168 }
169 writeBytes(TWO_HYPHENS, out);
170 writeBytes(boundaryEncoded, out);
171 writeBytes(TWO_HYPHENS, out);
172 writeBytes(CR_LF, out);
173 }
174
175
176
177
178 protected abstract void formatMultipartHeader(
179 final MultipartPart part,
180 final OutputStream out) throws IOException;
181
182
183
184
185
186
187 public void writeTo(final OutputStream out) throws IOException {
188 doWriteTo(out, true);
189 }
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205 public long getTotalLength() {
206 long contentLen = 0;
207 for (final MultipartPart part: getParts()) {
208 final ContentBody body = part.getBody();
209 final long len = body.getContentLength();
210 if (len >= 0) {
211 contentLen += len;
212 } else {
213 return -1;
214 }
215 }
216 final ByteArrayOutputStream out = new ByteArrayOutputStream();
217 try {
218 doWriteTo(out, false);
219 final byte[] extra = out.toByteArray();
220 return contentLen + extra.length;
221 } catch (final IOException ex) {
222
223 return -1;
224 }
225 }
226
227 }