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