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.charset.Charset;
34 import java.nio.charset.CharsetEncoder;
35 import java.nio.charset.StandardCharsets;
36 import java.util.BitSet;
37 import java.util.List;
38
39 import org.apache.hc.core5.http.NameValuePair;
40 import org.apache.hc.core5.util.ByteArrayBuffer;
41
42 class HttpRFC7578Multipart extends AbstractMultipartFormat {
43
44 private static final PercentCodec PERCENT_CODEC = new PercentCodec();
45
46 private final List<MultipartPart> parts;
47
48
49
50
51
52
53
54
55
56
57 public HttpRFC7578Multipart(
58 final Charset charset,
59 final String boundary,
60 final List<MultipartPart> parts,
61 final String preamble,
62 final String epilogue) {
63 super(charset, boundary, preamble, epilogue);
64 this.parts = parts;
65 }
66
67
68
69
70
71
72
73
74 public HttpRFC7578Multipart(
75 final Charset charset,
76 final String boundary,
77 final List<MultipartPart> parts) {
78 this(charset,boundary,parts,null, null);
79 }
80
81 @Override
82 public List<MultipartPart> getParts() {
83 return parts;
84 }
85
86 @Override
87 protected void formatMultipartHeader(final MultipartPart part, final OutputStream out) throws IOException {
88 for (final MimeField field: part.getHeader()) {
89 if (MimeConsts.CONTENT_DISPOSITION.equalsIgnoreCase(field.getName())) {
90 writeBytes(field.getName(), charset, out);
91 writeBytes(FIELD_SEP, out);
92 writeBytes(field.getValue(), out);
93 final List<NameValuePair> parameters = field.getParameters();
94 for (int i = 0; i < parameters.size(); i++) {
95 final NameValuePair parameter = parameters.get(i);
96 final String name = parameter.getName();
97 final String value = parameter.getValue();
98 writeBytes("; ", out);
99 writeBytes(name, out);
100 writeBytes("=\"", out);
101 if (value != null) {
102 if (name.equalsIgnoreCase(MimeConsts.FIELD_PARAM_FILENAME)) {
103 out.write(PERCENT_CODEC.encode(value.getBytes(charset)));
104 } else {
105 writeBytes(value, out);
106 }
107 }
108 writeBytes("\"", out);
109 }
110 writeBytes(CR_LF, out);
111 } else {
112 writeField(field, charset, out);
113 }
114 }
115 }
116
117 static class PercentCodec {
118
119 private static final byte ESCAPE_CHAR = '%';
120
121 private static final BitSet ALWAYSENCODECHARS = new BitSet();
122
123 static {
124 ALWAYSENCODECHARS.set(' ');
125 ALWAYSENCODECHARS.set('%');
126 }
127
128
129
130
131 public byte[] encode(final byte[] bytes) {
132 if (bytes == null) {
133 return null;
134 }
135
136 final CharsetEncoder characterSetEncoder = StandardCharsets.US_ASCII.newEncoder();
137 final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
138 for (final byte c : bytes) {
139 int b = c;
140 if (b < 0) {
141 b = 256 + b;
142 }
143 if (characterSetEncoder.canEncode((char) b) && !ALWAYSENCODECHARS.get(c)) {
144 buffer.write(b);
145 } else {
146 buffer.write(ESCAPE_CHAR);
147 final char hex1 = hexDigit(b >> 4);
148 final char hex2 = hexDigit(b);
149 buffer.write(hex1);
150 buffer.write(hex2);
151 }
152 }
153 return buffer.toByteArray();
154 }
155
156 public byte[] decode(final byte[] bytes) {
157 if (bytes == null) {
158 return null;
159 }
160 final ByteArrayBuffer buffer = new ByteArrayBuffer(bytes.length);
161 for (int i = 0; i < bytes.length; i++) {
162 final int b = bytes[i];
163 if (b == ESCAPE_CHAR) {
164 if (i >= bytes.length - 2) {
165 throw new IllegalArgumentException("Invalid encoding: too short");
166 }
167 final int u = digit16(bytes[++i]);
168 final int l = digit16(bytes[++i]);
169 buffer.append((char) ((u << 4) + l));
170 } else {
171 buffer.append(b);
172 }
173 }
174 return buffer.toByteArray();
175 }
176 }
177
178
179
180
181 private static final int RADIX = 16;
182
183
184
185
186
187
188
189
190
191
192
193 static int digit16(final byte b) {
194 final int i = Character.digit((char) b, RADIX);
195 if (i == -1) {
196 throw new IllegalArgumentException("Invalid encoding: not a valid digit (radix " + RADIX + "): " + b);
197 }
198 return i;
199 }
200
201
202
203
204
205
206
207 static char hexDigit(final int b) {
208 return Character.toUpperCase(Character.forDigit(b & 0xF, RADIX));
209 }
210
211 }