• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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