1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 /* 3 * Copyright (C) 2014 Square, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package com.android.okhttp; 18 19 import com.android.okhttp.internal.Util; 20 import java.io.IOException; 21 import java.util.ArrayList; 22 import java.util.List; 23 import java.util.UUID; 24 import com.android.okhttp.okio.Buffer; 25 import com.android.okhttp.okio.BufferedSink; 26 import com.android.okhttp.okio.ByteString; 27 28 /** 29 * Fluent API to build <a href="http://www.ietf.org/rfc/rfc2387.txt">RFC 30 * 2387</a>-compliant request bodies. 31 * @hide This class is not part of the Android public SDK API 32 */ 33 public final class MultipartBuilder { 34 /** 35 * The "mixed" subtype of "multipart" is intended for use when the body 36 * parts are independent and need to be bundled in a particular order. Any 37 * "multipart" subtypes that an implementation does not recognize must be 38 * treated as being of subtype "mixed". 39 */ 40 public static final MediaType MIXED = MediaType.parse("multipart/mixed"); 41 42 /** 43 * The "multipart/alternative" type is syntactically identical to 44 * "multipart/mixed", but the semantics are different. In particular, each 45 * of the body parts is an "alternative" version of the same information. 46 */ 47 public static final MediaType ALTERNATIVE = MediaType.parse("multipart/alternative"); 48 49 /** 50 * This type is syntactically identical to "multipart/mixed", but the 51 * semantics are different. In particular, in a digest, the default {@code 52 * Content-Type} value for a body part is changed from "text/plain" to 53 * "message/rfc822". 54 */ 55 public static final MediaType DIGEST = MediaType.parse("multipart/digest"); 56 57 /** 58 * This type is syntactically identical to "multipart/mixed", but the 59 * semantics are different. In particular, in a parallel entity, the order 60 * of body parts is not significant. 61 */ 62 public static final MediaType PARALLEL = MediaType.parse("multipart/parallel"); 63 64 /** 65 * The media-type multipart/form-data follows the rules of all multipart 66 * MIME data streams as outlined in RFC 2046. In forms, there are a series 67 * of fields to be supplied by the user who fills out the form. Each field 68 * has a name. Within a given form, the names are unique. 69 */ 70 public static final MediaType FORM = MediaType.parse("multipart/form-data"); 71 72 private static final byte[] COLONSPACE = { ':', ' ' }; 73 private static final byte[] CRLF = { '\r', '\n' }; 74 private static final byte[] DASHDASH = { '-', '-' }; 75 76 private final ByteString boundary; 77 private MediaType type = MIXED; 78 79 // Parallel lists of nullable headers and non-null bodies. 80 private final List<Headers> partHeaders = new ArrayList<>(); 81 private final List<RequestBody> partBodies = new ArrayList<>(); 82 83 /** Creates a new multipart builder that uses a random boundary token. */ MultipartBuilder()84 public MultipartBuilder() { 85 this(UUID.randomUUID().toString()); 86 } 87 88 /** 89 * Creates a new multipart builder that uses {@code boundary} to separate 90 * parts. Prefer the no-argument constructor to defend against injection 91 * attacks. 92 */ MultipartBuilder(String boundary)93 public MultipartBuilder(String boundary) { 94 this.boundary = ByteString.encodeUtf8(boundary); 95 } 96 97 /** 98 * Set the MIME type. Expected values for {@code type} are {@link #MIXED} (the 99 * default), {@link #ALTERNATIVE}, {@link #DIGEST}, {@link #PARALLEL} and 100 * {@link #FORM}. 101 */ type(MediaType type)102 public MultipartBuilder type(MediaType type) { 103 if (type == null) { 104 throw new NullPointerException("type == null"); 105 } 106 if (!type.type().equals("multipart")) { 107 throw new IllegalArgumentException("multipart != " + type); 108 } 109 this.type = type; 110 return this; 111 } 112 113 /** Add a part to the body. */ addPart(RequestBody body)114 public MultipartBuilder addPart(RequestBody body) { 115 return addPart(null, body); 116 } 117 118 /** Add a part to the body. */ addPart(Headers headers, RequestBody body)119 public MultipartBuilder addPart(Headers headers, RequestBody body) { 120 if (body == null) { 121 throw new NullPointerException("body == null"); 122 } 123 if (headers != null && headers.get("Content-Type") != null) { 124 throw new IllegalArgumentException("Unexpected header: Content-Type"); 125 } 126 if (headers != null && headers.get("Content-Length") != null) { 127 throw new IllegalArgumentException("Unexpected header: Content-Length"); 128 } 129 130 partHeaders.add(headers); 131 partBodies.add(body); 132 return this; 133 } 134 135 /** 136 * Appends a quoted-string to a StringBuilder. 137 * 138 * <p>RFC 2388 is rather vague about how one should escape special characters 139 * in form-data parameters, and as it turns out Firefox and Chrome actually 140 * do rather different things, and both say in their comments that they're 141 * not really sure what the right approach is. We go with Chrome's behavior 142 * (which also experimentally seems to match what IE does), but if you 143 * actually want to have a good chance of things working, please avoid 144 * double-quotes, newlines, percent signs, and the like in your field names. 145 */ appendQuotedString(StringBuilder target, String key)146 private static StringBuilder appendQuotedString(StringBuilder target, String key) { 147 target.append('"'); 148 for (int i = 0, len = key.length(); i < len; i++) { 149 char ch = key.charAt(i); 150 switch (ch) { 151 case '\n': 152 target.append("%0A"); 153 break; 154 case '\r': 155 target.append("%0D"); 156 break; 157 case '"': 158 target.append("%22"); 159 break; 160 default: 161 target.append(ch); 162 break; 163 } 164 } 165 target.append('"'); 166 return target; 167 } 168 169 /** Add a form data part to the body. */ addFormDataPart(String name, String value)170 public MultipartBuilder addFormDataPart(String name, String value) { 171 return addFormDataPart(name, null, RequestBody.create(null, value)); 172 } 173 174 /** Add a form data part to the body. */ addFormDataPart(String name, String filename, RequestBody value)175 public MultipartBuilder addFormDataPart(String name, String filename, RequestBody value) { 176 if (name == null) { 177 throw new NullPointerException("name == null"); 178 } 179 StringBuilder disposition = new StringBuilder("form-data; name="); 180 appendQuotedString(disposition, name); 181 182 if (filename != null) { 183 disposition.append("; filename="); 184 appendQuotedString(disposition, filename); 185 } 186 187 return addPart(Headers.of("Content-Disposition", disposition.toString()), value); 188 } 189 190 /** Assemble the specified parts into a request body. */ build()191 public RequestBody build() { 192 if (partHeaders.isEmpty()) { 193 throw new IllegalStateException("Multipart body must have at least one part."); 194 } 195 return new MultipartRequestBody(type, boundary, partHeaders, partBodies); 196 } 197 198 private static final class MultipartRequestBody extends RequestBody { 199 private final ByteString boundary; 200 private final MediaType contentType; 201 private final List<Headers> partHeaders; 202 private final List<RequestBody> partBodies; 203 private long contentLength = -1L; 204 MultipartRequestBody(MediaType type, ByteString boundary, List<Headers> partHeaders, List<RequestBody> partBodies)205 public MultipartRequestBody(MediaType type, ByteString boundary, List<Headers> partHeaders, 206 List<RequestBody> partBodies) { 207 if (type == null) throw new NullPointerException("type == null"); 208 209 this.boundary = boundary; 210 this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8()); 211 this.partHeaders = Util.immutableList(partHeaders); 212 this.partBodies = Util.immutableList(partBodies); 213 } 214 contentType()215 @Override public MediaType contentType() { 216 return contentType; 217 } 218 contentLength()219 @Override public long contentLength() throws IOException { 220 long result = contentLength; 221 if (result != -1L) return result; 222 return contentLength = writeOrCountBytes(null, true); 223 } 224 225 /** 226 * Either writes this request to {@code sink} or measures its content length. We have one method 227 * do double-duty to make sure the counting and content are consistent, particularly when it 228 * comes to awkward operations like measuring the encoded length of header strings, or the 229 * length-in-digits of an encoded integer. 230 */ writeOrCountBytes(BufferedSink sink, boolean countBytes)231 private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException { 232 long byteCount = 0L; 233 234 Buffer byteCountBuffer = null; 235 if (countBytes) { 236 sink = byteCountBuffer = new Buffer(); 237 } 238 239 for (int p = 0, partCount = partHeaders.size(); p < partCount; p++) { 240 Headers headers = partHeaders.get(p); 241 RequestBody body = partBodies.get(p); 242 243 sink.write(DASHDASH); 244 sink.write(boundary); 245 sink.write(CRLF); 246 247 if (headers != null) { 248 for (int h = 0, headerCount = headers.size(); h < headerCount; h++) { 249 sink.writeUtf8(headers.name(h)) 250 .write(COLONSPACE) 251 .writeUtf8(headers.value(h)) 252 .write(CRLF); 253 } 254 } 255 256 MediaType contentType = body.contentType(); 257 if (contentType != null) { 258 sink.writeUtf8("Content-Type: ") 259 .writeUtf8(contentType.toString()) 260 .write(CRLF); 261 } 262 263 long contentLength = body.contentLength(); 264 if (contentLength != -1) { 265 sink.writeUtf8("Content-Length: ") 266 .writeDecimalLong(contentLength) 267 .write(CRLF); 268 } else if (countBytes) { 269 // We can't measure the body's size without the sizes of its components. 270 byteCountBuffer.clear(); 271 return -1L; 272 } 273 274 sink.write(CRLF); 275 276 if (countBytes) { 277 byteCount += contentLength; 278 } else { 279 partBodies.get(p).writeTo(sink); 280 } 281 282 sink.write(CRLF); 283 } 284 285 sink.write(DASHDASH); 286 sink.write(boundary); 287 sink.write(DASHDASH); 288 sink.write(CRLF); 289 290 if (countBytes) { 291 byteCount += byteCountBuffer.size(); 292 byteCountBuffer.clear(); 293 } 294 295 return byteCount; 296 } 297 writeTo(BufferedSink sink)298 @Override public void writeTo(BufferedSink sink) throws IOException { 299 writeOrCountBytes(sink, false); 300 } 301 } 302 } 303