1/*
2 * Copyright (C) 2014 Square, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.squareup.okhttp;
17
18import java.io.IOException;
19import okio.Buffer;
20import okio.BufferedSink;
21import org.junit.Test;
22
23import static com.squareup.okhttp.internal.Util.UTF_8;
24import static org.junit.Assert.assertEquals;
25import static org.junit.Assert.fail;
26
27public final class MultipartBuilderTest {
28  @Test(expected = IllegalStateException.class)
29  public void onePartRequired() throws Exception {
30    new MultipartBuilder().build();
31  }
32
33  @Test public void singlePart() throws Exception {
34    String expected = ""
35        + "--123\r\n"
36        + "Content-Length: 13\r\n"
37        + "\r\n"
38        + "Hello, World!\r\n"
39        + "--123--\r\n";
40
41    RequestBody requestBody = new MultipartBuilder("123")
42        .addPart(RequestBody.create(null, "Hello, World!"))
43        .build();
44
45    assertEquals("multipart/mixed; boundary=123", requestBody.contentType().toString());
46
47    Buffer buffer = new Buffer();
48    requestBody.writeTo(buffer);
49    assertEquals(53, requestBody.contentLength());
50    assertEquals(buffer.size(), requestBody.contentLength());
51    assertEquals(expected, buffer.readUtf8());
52  }
53
54  @Test public void threeParts() throws Exception {
55    String expected = ""
56        + "--123\r\n"
57        + "Content-Length: 5\r\n"
58        + "\r\n"
59        + "Quick\r\n"
60        + "--123\r\n"
61        + "Content-Length: 5\r\n"
62        + "\r\n"
63        + "Brown\r\n"
64        + "--123\r\n"
65        + "Content-Length: 3\r\n"
66        + "\r\n"
67        + "Fox\r\n"
68        + "--123--\r\n";
69
70    RequestBody requestBody = new MultipartBuilder("123")
71        .addPart(RequestBody.create(null, "Quick"))
72        .addPart(RequestBody.create(null, "Brown"))
73        .addPart(RequestBody.create(null, "Fox"))
74        .build();
75
76    assertEquals("multipart/mixed; boundary=123", requestBody.contentType().toString());
77
78    Buffer buffer = new Buffer();
79    requestBody.writeTo(buffer);
80    assertEquals(112, requestBody.contentLength());
81    assertEquals(buffer.size(), requestBody.contentLength());
82    assertEquals(expected, buffer.readUtf8());
83  }
84
85  @Test public void fieldAndTwoFiles() throws Exception {
86    String expected = ""
87        + "--AaB03x\r\n"
88        + "Content-Disposition: form-data; name=\"submit-name\"\r\n"
89        + "Content-Length: 5\r\n"
90        + "\r\n"
91        + "Larry\r\n"
92        + "--AaB03x\r\n"
93        + "Content-Disposition: form-data; name=\"files\"\r\n"
94        + "Content-Type: multipart/mixed; boundary=BbC04y\r\n"
95        + "Content-Length: 337\r\n"
96        + "\r\n"
97        + "--BbC04y\r\n"
98        + "Content-Disposition: file; filename=\"file1.txt\"\r\n"
99        + "Content-Type: text/plain; charset=utf-8\r\n"
100        + "Content-Length: 29\r\n"
101        + "\r\n"
102        + "... contents of file1.txt ...\r\n"
103        + "--BbC04y\r\n"
104        + "Content-Disposition: file; filename=\"file2.gif\"\r\n"
105        + "Content-Transfer-Encoding: binary\r\n"
106        + "Content-Type: image/gif\r\n"
107        + "Content-Length: 29\r\n"
108        + "\r\n"
109        + "... contents of file2.gif ...\r\n"
110        + "--BbC04y--\r\n"
111        + "\r\n"
112        + "--AaB03x--\r\n";
113
114    RequestBody requestBody = new MultipartBuilder("AaB03x")
115        .type(MultipartBuilder.FORM)
116        .addFormDataPart("submit-name", "Larry")
117        .addFormDataPart("files", null,
118            new MultipartBuilder("BbC04y")
119                .addPart(
120                    Headers.of("Content-Disposition", "file; filename=\"file1.txt\""),
121                    RequestBody.create(
122                        MediaType.parse("text/plain"), "... contents of file1.txt ..."))
123                .addPart(
124                    Headers.of(
125                        "Content-Disposition", "file; filename=\"file2.gif\"",
126                        "Content-Transfer-Encoding", "binary"),
127                    RequestBody.create(
128                        MediaType.parse("image/gif"),
129                        "... contents of file2.gif ...".getBytes(UTF_8)))
130                .build())
131        .build();
132
133    assertEquals("multipart/form-data; boundary=AaB03x", requestBody.contentType().toString());
134
135    Buffer buffer = new Buffer();
136    requestBody.writeTo(buffer);
137    assertEquals(568, requestBody.contentLength());
138    assertEquals(buffer.size(), requestBody.contentLength());
139    assertEquals(expected, buffer.readUtf8());
140  }
141
142  @Test public void stringEscapingIsWeird() throws Exception {
143    String expected = ""
144        + "--AaB03x\r\n"
145        + "Content-Disposition: form-data; name=\"field with spaces\"; filename=\"filename with spaces.txt\"\r\n"
146        + "Content-Type: text/plain; charset=utf-8\r\n"
147        + "Content-Length: 4\r\n"
148        + "\r\n"
149        + "okay\r\n"
150        + "--AaB03x\r\n"
151        + "Content-Disposition: form-data; name=\"field with %22\"\r\n"
152        + "Content-Length: 1\r\n"
153        + "\r\n"
154        + "\"\r\n"
155        + "--AaB03x\r\n"
156        + "Content-Disposition: form-data; name=\"field with %22\"\r\n"
157        + "Content-Length: 3\r\n"
158        + "\r\n"
159        + "%22\r\n"
160        + "--AaB03x\r\n"
161        + "Content-Disposition: form-data; name=\"field with \u0391\"\r\n"
162        + "Content-Length: 5\r\n"
163        + "\r\n"
164        + "Alpha\r\n"
165        + "--AaB03x--\r\n";
166
167    RequestBody requestBody = new MultipartBuilder("AaB03x")
168        .type(MultipartBuilder.FORM)
169        .addFormDataPart("field with spaces", "filename with spaces.txt",
170            RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), "okay"))
171        .addFormDataPart("field with \"", "\"")
172        .addFormDataPart("field with %22", "%22")
173        .addFormDataPart("field with \u0391", "Alpha")
174        .build();
175
176    Buffer buffer = new Buffer();
177    requestBody.writeTo(buffer);
178    assertEquals(expected, buffer.readUtf8());
179  }
180
181  @Test public void streamingPartHasNoLength() throws Exception {
182    class StreamingBody extends RequestBody {
183      private final String body;
184
185      StreamingBody(String body) {
186        this.body = body;
187      }
188
189      @Override public MediaType contentType() {
190        return null;
191      }
192
193      @Override public void writeTo(BufferedSink sink) throws IOException {
194        sink.writeUtf8(body);
195      }
196    }
197
198    String expected = ""
199        + "--123\r\n"
200        + "Content-Length: 5\r\n"
201        + "\r\n"
202        + "Quick\r\n"
203        + "--123\r\n"
204        + "\r\n"
205        + "Brown\r\n"
206        + "--123\r\n"
207        + "Content-Length: 3\r\n"
208        + "\r\n"
209        + "Fox\r\n"
210        + "--123--\r\n";
211
212    RequestBody requestBody = new MultipartBuilder("123")
213        .addPart(RequestBody.create(null, "Quick"))
214        .addPart(new StreamingBody("Brown"))
215        .addPart(RequestBody.create(null, "Fox"))
216        .build();
217
218    assertEquals("multipart/mixed; boundary=123", requestBody.contentType().toString());
219
220    Buffer buffer = new Buffer();
221    requestBody.writeTo(buffer);
222    assertEquals(expected, buffer.readUtf8());
223    assertEquals(-1, requestBody.contentLength());
224  }
225
226  @Test public void contentTypeHeaderIsForbidden() throws Exception {
227    try {
228      new MultipartBuilder().addPart(
229          Headers.of("Content-Type", "text/plain"),
230          RequestBody.create(null, "Hello, World!"));
231      fail();
232    } catch (IllegalArgumentException expected) {
233    }
234  }
235
236  @Test public void contentLengthHeaderIsForbidden() throws Exception {
237    try {
238      new MultipartBuilder().addPart(
239          Headers.of("Content-Length", "13"),
240          RequestBody.create(null, "Hello, World!"));
241      fail();
242    } catch (IllegalArgumentException expected) {
243    }
244  }
245}
246