1 /*
2 * ====================================================================
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 * ====================================================================
20 *
21 * This software consists of voluntary contributions made by many
22 * individuals on behalf of the Apache Software Foundation. For more
23 * information on the Apache Software Foundation, please see
24 * <http://www.apache.org/>.
25 *
26 */
27
28 package org.apache.hc.client5.http.entity.mime;
29
30 import java.io.IOException;
31 import java.io.OutputStream;
32 import java.nio.charset.Charset;
33 import java.nio.charset.StandardCharsets;
34 import java.util.List;
35
36 import org.apache.hc.core5.http.NameValuePair;
37 import org.apache.hc.core5.net.PercentCodec;
38
39 class HttpRFC7578Multipart extends AbstractMultipartFormat {
40
41 private final List<MultipartPart> parts;
42
43 private final HttpMultipartMode mode;
44
45 /**
46 * Constructs a new instance of {@code HttpRFC7578Multipart} with the given charset, boundary, parts, preamble, and epilogue.
47 *
48 * @param charset the charset to use.
49 * @param boundary the boundary string to use.
50 * @param parts the list of parts to include in the multipart message.
51 * @param preamble the optional preamble string to include before the first part. May be {@code null}.
52 * @param epilogue the optional epilogue string to include after the last part. May be {@code null}.
53 */
54 public HttpRFC7578Multipart(
55 final Charset charset,
56 final String boundary,
57 final List<MultipartPart> parts,
58 final String preamble,
59 final String epilogue,
60 final HttpMultipartMode mode) {
61 super(charset, boundary, preamble, epilogue);
62 this.parts = parts;
63 this.mode = mode != null ? mode : HttpMultipartMode.STRICT; // Default to STRICT
64 }
65
66 /**
67 * Constructs a new instance of {@code HttpRFC7578Multipart} with the given charset, boundary, and parts.
68 *
69 * @param charset the charset to use.
70 * @param boundary the boundary string to use.
71 * @param parts the list of parts to include in the multipart message.
72 */
73 public HttpRFC7578Multipart(
74 final Charset charset,
75 final String boundary,
76 final List<MultipartPart> parts,
77 final HttpMultipartMode mode) {
78 this(charset,boundary,parts,null, null, mode);
79 }
80
81
82 @Override
83 public List<MultipartPart> getParts() {
84 return parts;
85 }
86
87 @Override
88 protected void formatMultipartHeader(final MultipartPart part, final OutputStream out) throws IOException {
89 for (final MimeField field: part.getHeader()) {
90 if (MimeConsts.CONTENT_DISPOSITION.equalsIgnoreCase(field.getName())) {
91 writeBytes(field.getName(), charset, out);
92 writeBytes(FIELD_SEP, out);
93 writeBytes(field.getValue(), out);
94 final List<NameValuePair> parameters = field.getParameters();
95 for (int i = 0; i < parameters.size(); i++) {
96 final NameValuePair parameter = parameters.get(i);
97 final String name = parameter.getName();
98 final String value = parameter.getValue();
99 writeBytes("; ", out);
100 writeBytes(name, out);
101 writeBytes("=\"", out);
102 if (value != null) {
103 if (name.equalsIgnoreCase(MimeConsts.FIELD_PARAM_FILENAME_START)) {
104 final String encodedValue = "UTF-8''" + PercentCodec.RFC5987.encode(value);
105 writeBytes(encodedValue, StandardCharsets.US_ASCII, out);
106 } else if (name.equalsIgnoreCase(MimeConsts.FIELD_PARAM_FILENAME)) {
107 if (mode == HttpMultipartMode.EXTENDED) {
108 final String encodedValue = PercentCodec.RFC5987.encode(value);
109 writeBytes(encodedValue, StandardCharsets.US_ASCII, out);
110 } else {
111 // Default to ISO-8859-1 for RFC 7578 compliance in STRICT/LEGACY
112 writeBytes(value, StandardCharsets.ISO_8859_1, out);
113 }
114 } else {
115 writeBytes(value, out);
116 }
117 }
118 writeBytes("\"", out);
119 }
120 writeBytes(CR_LF, out);
121 } else {
122 writeField(field, charset, out);
123 }
124 }
125 }
126
127 }