From 6a2242ce3077291ee4311a91bf69df08cb15e678 Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Sat, 7 Jun 2014 09:44:56 -0700 Subject: [PATCH] Import mimecraft as-is. --- .../squareup/okhttp/ComplexExamplesTest.java | 70 +++++ .../com/squareup/okhttp/FormWriterTest.java | 47 ++++ .../squareup/okhttp/MultipartWriterTest.java | 63 +++++ .../java/com/squareup/okhttp/PartTest.java | 152 ++++++++++ .../java/com/squareup/okhttp/TestPart.java | 25 ++ .../java/com/squareup/okhttp/TestUtils.java | 11 + .../com/squareup/okhttp/FormEncoding.java | 64 +++++ .../java/com/squareup/okhttp/Multipart.java | 155 +++++++++++ .../main/java/com/squareup/okhttp/Part.java | 260 ++++++++++++++++++ .../main/java/com/squareup/okhttp/Utils.java | 44 +++ 10 files changed, 891 insertions(+) create mode 100644 okhttp-tests/src/test/java/com/squareup/okhttp/ComplexExamplesTest.java create mode 100644 okhttp-tests/src/test/java/com/squareup/okhttp/FormWriterTest.java create mode 100644 okhttp-tests/src/test/java/com/squareup/okhttp/MultipartWriterTest.java create mode 100644 okhttp-tests/src/test/java/com/squareup/okhttp/PartTest.java create mode 100644 okhttp-tests/src/test/java/com/squareup/okhttp/TestPart.java create mode 100644 okhttp-tests/src/test/java/com/squareup/okhttp/TestUtils.java create mode 100644 okhttp/src/main/java/com/squareup/okhttp/FormEncoding.java create mode 100644 okhttp/src/main/java/com/squareup/okhttp/Multipart.java create mode 100644 okhttp/src/main/java/com/squareup/okhttp/Part.java create mode 100644 okhttp/src/main/java/com/squareup/okhttp/Utils.java diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/ComplexExamplesTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/ComplexExamplesTest.java new file mode 100644 index 000000000..142fa9d61 --- /dev/null +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/ComplexExamplesTest.java @@ -0,0 +1,70 @@ +// Copyright 2013 Square, Inc. +package com.squareup.okhttp; + +import java.io.ByteArrayOutputStream; +import java.util.Collections; +import org.junit.Test; + +import static com.squareup.okhttp.TestUtils.UTF_8; +import static org.junit.Assert.assertEquals; + +public class ComplexExamplesTest { + @Test public void fieldAndTwoFiles() throws Exception { + String expected = "" + + "--AaB03x\r\n" + + "Content-Disposition: form-data; name=\"submit-name\"\r\n" + + "Content-Length: 5\r\n" + + "\r\n" + + "Larry\r\n" + + "--AaB03x\r\n" + + "Content-Disposition: form-data; name=\"files\"\r\n" + + "Content-Type: multipart/mixed; boundary=BbC04y\r\n" + + "\r\n" + + "--BbC04y\r\n" + + "Content-Disposition: file; filename=\"file1.txt\"\r\n" + + "Content-Type: text/plain\r\n" + + "Content-Length: 29\r\n" + + "\r\n" + + "... contents of file1.txt ...\r\n" + + "--BbC04y\r\n" + + "Content-Disposition: file; filename=\"file2.gif\"\r\n" + + "Content-Type: image/gif\r\n" + + "Content-Length: 29\r\n" + + "Content-Transfer-Encoding: binary\r\n" + + "\r\n" + + "... contents of file2.gif ...\r\n" + + "--BbC04y--\r\n" + + "--AaB03x--"; + + Multipart m = new Multipart.Builder("AaB03x") // + .type(Multipart.Type.FORM) // + .addPart(new Part.Builder() // + .contentDisposition("form-data; name=\"submit-name\"") // + .body("Larry") // + .build()) // + .addPart(new Part.Builder() // + .contentDisposition("form-data; name=\"files\"") // + .body(new Multipart.Builder("BbC04y") // + .addPart(new Part.Builder() // + .contentDisposition("file; filename=\"file1.txt\"") // + .contentType("text/plain") // + .body("... contents of file1.txt ...") // + .build()) // + .addPart(new Part.Builder() // + .contentDisposition("file; filename=\"file2.gif\"") // + .contentType("image/gif") // + .contentEncoding("binary") // + .body("... contents of file2.gif ...") // + .build()) // + .build()) // + .build()) // + .build(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + m.writeBodyTo(out); + String actual = new String(out.toByteArray(), UTF_8); + assertEquals(expected, actual); + assertEquals(Collections.singletonMap("Content-Type", "multipart/form-data; boundary=AaB03x"), + m.getHeaders()); + } +} diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/FormWriterTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/FormWriterTest.java new file mode 100644 index 000000000..8505c6f99 --- /dev/null +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/FormWriterTest.java @@ -0,0 +1,47 @@ +// Copyright 2013 Square, Inc. +package com.squareup.okhttp; + +import java.io.ByteArrayOutputStream; +import java.util.Collections; +import org.junit.Test; + +import static com.squareup.okhttp.TestUtils.UTF_8; +import static org.junit.Assert.assertEquals; + +public class FormWriterTest { + @Test public void urlEncoding() throws Exception { + FormEncoding fe = new FormEncoding.Builder() // + .add("a&b", "c=d") // + .add("space, the", "final frontier") // + .build(); + + assertEquals(Collections.singletonMap("Content-Type", "application/x-www-form-urlencoded"), + fe.getHeaders()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + fe.writeBodyTo(out); + String actual = new String(out.toByteArray(), UTF_8); + assertEquals("a%26b=c%3Dd&space%2C+the=final+frontier", actual); + } + + @Test public void encodedPairs() throws Exception { + FormEncoding fe1 = new FormEncoding.Builder() // + .add("sim", "ple") // + .build(); + + ByteArrayOutputStream out1 = new ByteArrayOutputStream(); + fe1.writeBodyTo(out1); + String actual1 = new String(out1.toByteArray(), UTF_8); + assertEquals("sim=ple", actual1); + + FormEncoding fe2 = new FormEncoding.Builder() // + .add("sim", "ple") // + .add("hey", "there") // + .add("help", "me") // + .build(); + + ByteArrayOutputStream out2 = new ByteArrayOutputStream(); + fe2.writeBodyTo(out2); + String actual2 = new String(out2.toByteArray(), UTF_8); + assertEquals("sim=ple&hey=there&help=me", actual2); + } +} diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/MultipartWriterTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/MultipartWriterTest.java new file mode 100644 index 000000000..5d8bae88f --- /dev/null +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/MultipartWriterTest.java @@ -0,0 +1,63 @@ +// Copyright 2013 Square, Inc. +package com.squareup.okhttp; + +import java.io.ByteArrayOutputStream; +import java.util.Collections; +import org.junit.Test; + +import static com.squareup.okhttp.TestUtils.UTF_8; +import static org.junit.Assert.assertEquals; + + +public class MultipartWriterTest { + @Test(expected = IllegalStateException.class) + public void onePartRequired() throws Exception { + new Multipart.Builder().build(); + } + + @Test public void singlePart() throws Exception { + String expected = "" // + + "--123\r\n" // + + "\r\n" // + + "Hello, World!\r\n" // + + "--123--"; + + Multipart m = new Multipart.Builder("123") + .addPart(new TestPart("Hello, World!")) + .build(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + m.writeBodyTo(out); + String actual = new String(out.toByteArray(), UTF_8); + assertEquals(expected, actual); + assertEquals(Collections.singletonMap("Content-Type", "multipart/mixed; boundary=123"), + m.getHeaders()); + } + + @Test public void threeParts() throws Exception { + String expected = "" + + "--123\r\n" + + "\r\n" + + "Quick\r\n" + + "--123\r\n" + + "\r\n" + + "Brown\r\n" + + "--123\r\n" + + "\r\n" + + "Fox\r\n" + + "--123--"; + + Multipart m = new Multipart.Builder("123") + .addPart(new TestPart("Quick")) + .addPart(new TestPart("Brown")) + .addPart(new TestPart("Fox")) + .build(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + m.writeBodyTo(out); + String actual = new String(out.toByteArray(), UTF_8); + assertEquals(expected, actual); + assertEquals(Collections.singletonMap("Content-Type", "multipart/mixed; boundary=123"), + m.getHeaders()); + } +} diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/PartTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/PartTest.java new file mode 100644 index 000000000..d39fde120 --- /dev/null +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/PartTest.java @@ -0,0 +1,152 @@ +// Copyright 2013 Square, Inc. +package com.squareup.okhttp; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class PartTest { + @Test(expected = IllegalStateException.class) + public void noBodyThrowsException() { + new Part.Builder().build(); + } + + @Test public void settingHeaderTwiceThrowsException() { + try { + new Part.Builder().contentEncoding("foo").contentEncoding("bar"); + fail(); + } catch (IllegalStateException expected) { + } + try { + new Part.Builder().contentLanguage("foo").contentLanguage("bar"); + fail(); + } catch (IllegalStateException expected) { + } + try { + new Part.Builder().contentLength(42).contentLength(108); + fail(); + } catch (IllegalStateException expected) { + } + try { + new Part.Builder().contentType("foo").contentType("bar"); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void settingBodyTwiceThrowsException() { + try { + new Part.Builder().body("foo").body("bar"); + fail(); + } catch (IllegalStateException expected) { + } + try { + byte[] bytes = { 'a', 'b' }; + new Part.Builder().body(bytes).body(bytes); + fail(); + } catch (IllegalStateException expected) { + } + try { + InputStream is = new ByteArrayInputStream(new byte[0]); + new Part.Builder().body(is).body(is); + fail(); + } catch (IllegalStateException expected) { + } + try { + File file = new File("/foo/bar"); + new Part.Builder().body(file).body(file); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void blankHeadersThrowsException() { + try { + new Part.Builder().contentType(""); + fail(); + } catch (IllegalStateException expected) { + } + try { + new Part.Builder().contentLength(0); + fail(); + } catch (IllegalStateException expected) { + } + try { + new Part.Builder().contentLength(-1); + fail(); + } catch (IllegalStateException expected) { + } + try { + new Part.Builder().contentLanguage(""); + fail(); + } catch (IllegalStateException expected) { + } + try { + new Part.Builder().contentEncoding(""); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void bodyBytesSetsContentLength() { + Part.Builder b = new Part.Builder(); + assertEquals(0, b.headerLength); + b.body("1234567"); + assertEquals(7, b.headerLength); + } + + @Test public void bodyBytesOverridesLength() { + Part.Builder b = new Part.Builder(); + assertEquals(0, b.headerLength); + b.contentLength(108); + assertEquals(108, b.headerLength); + b.body("1234567"); + assertEquals(7, b.headerLength); + } + + @Test public void completePart() throws Exception { + Part p = new Part.Builder() // + .contentDisposition("form-data; filename=\"foo.txt\"") // + .contentType("application/json") // + .contentLength(13) // + .contentLanguage("English") // + .contentEncoding("UTF-8") // + .body("{'foo':'bar'}") // + .build(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + p.writeBodyTo(baos); + String actual = new String(baos.toByteArray(), TestUtils.UTF_8); + assertEquals("{'foo':'bar'}", actual); + + Map expectedHeaders = new LinkedHashMap(); + expectedHeaders.put("Content-Disposition", "form-data; filename=\"foo.txt\""); + expectedHeaders.put("Content-Type", "application/json"); + expectedHeaders.put("Content-Length", "13"); + expectedHeaders.put("Content-Language", "English"); + expectedHeaders.put("Content-Transfer-Encoding", "UTF-8"); + assertEquals(expectedHeaders, p.getHeaders()); + } + + @Test public void multipartBodySetsType() throws Exception { + Multipart m = new Multipart.Builder().addPart(new TestPart("hi")).build(); + + try { + new Part.Builder().body(m).contentType("break me!"); + fail(); + } catch (IllegalStateException expected) { + } + + Part p = new Part.Builder().body(m).build(); + assertEquals(Collections.singletonMap("Content-Type", m.getHeaders().get("Content-Type")), + p.getHeaders()); + } +} diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/TestPart.java b/okhttp-tests/src/test/java/com/squareup/okhttp/TestPart.java new file mode 100644 index 000000000..9951a45dc --- /dev/null +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/TestPart.java @@ -0,0 +1,25 @@ +// Copyright 2013 Square, Inc. +package com.squareup.okhttp; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; +import java.util.Map; + +import static com.squareup.okhttp.TestUtils.UTF_8; + +public class TestPart implements Part { + private final byte[] content; + + public TestPart(String content) { + this.content = content.getBytes(UTF_8); + } + + @Override public Map getHeaders() { + return Collections.emptyMap(); + } + + @Override public void writeBodyTo(OutputStream out) throws IOException { + out.write(content); + } +} diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/TestUtils.java b/okhttp-tests/src/test/java/com/squareup/okhttp/TestUtils.java new file mode 100644 index 000000000..e487267ea --- /dev/null +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/TestUtils.java @@ -0,0 +1,11 @@ +package com.squareup.okhttp; + +import java.nio.charset.Charset; + +final class TestUtils { + static final Charset UTF_8 = Charset.forName("UTF-8"); + + private TestUtils() { + // prevent instantiation + } +} diff --git a/okhttp/src/main/java/com/squareup/okhttp/FormEncoding.java b/okhttp/src/main/java/com/squareup/okhttp/FormEncoding.java new file mode 100644 index 000000000..70d9858bf --- /dev/null +++ b/okhttp/src/main/java/com/squareup/okhttp/FormEncoding.java @@ -0,0 +1,64 @@ +// Copyright 2013 Square, Inc. +package com.squareup.okhttp; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.Map; + +/** + * HTML 2.0-compliant + * form data. + */ +public final class FormEncoding implements Part { + private static final Map HEADERS = + Collections.singletonMap("Content-Type", "application/x-www-form-urlencoded"); + + /** Fluent API to build {@link FormEncoding} instances. */ + public static class Builder { + private final StringBuilder content = new StringBuilder(); + + /** Add new key-value pair. */ + public Builder add(String name, String value) { + if (content.length() > 0) { + content.append('&'); + } + try { + content.append(URLEncoder.encode(name, "UTF-8")) + .append('=') + .append(URLEncoder.encode(value, "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); + } + return this; + } + + /** Create {@link FormEncoding} instance. */ + public FormEncoding build() { + if (content.length() == 0) { + throw new IllegalStateException("Form encoded body must have at least one part."); + } + return new FormEncoding(content.toString()); + } + } + + private final byte[] data; + + private FormEncoding(String data) { + try { + this.data = data.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Unable to convert input to UTF-8: " + data, e); + } + } + + @Override public Map getHeaders() { + return HEADERS; + } + + @Override public void writeBodyTo(OutputStream stream) throws IOException { + stream.write(data); + } +} diff --git a/okhttp/src/main/java/com/squareup/okhttp/Multipart.java b/okhttp/src/main/java/com/squareup/okhttp/Multipart.java new file mode 100644 index 000000000..deba3416c --- /dev/null +++ b/okhttp/src/main/java/com/squareup/okhttp/Multipart.java @@ -0,0 +1,155 @@ +// Copyright 2013 Square, Inc. +package com.squareup.okhttp; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static com.squareup.okhttp.Utils.isNotNull; + +/** RFC 2387-compliant encoded data. */ +public final class Multipart implements Part { + + /** Multipart MIME types. */ + public enum Type { + /** + * The "mixed" subtype of "multipart" is intended for use when the body parts are independent + * and need to be bundled in a particular order. Any "multipart" subtypes that an + * implementation does not recognize must be treated as being of subtype "mixed". + */ + MIXED("mixed"), + /** + * The "multipart/alternative" type is syntactically identical to "multipart/mixed", but the + * semantics are different. In particular, each of the body parts is an "alternative" version + * of the same information. + */ + ALTERNATIVE("alternative"), + /** + * This type is syntactically identical to "multipart/mixed", but the semantics are different. + * In particular, in a digest, the default {@code Content-Type} value for a body part is + * changed from "text/plain" to "message/rfc822". + */ + DIGEST("digest"), + /** + * This type is syntactically identical to "multipart/mixed", but the semantics are different. + * In particular, in a parallel entity, the order of body parts is not significant. + */ + PARALLEL("parallel"), + /** + * The media-type multipart/form-data follows the rules of all multipart MIME data streams as + * outlined in RFC 2046. In forms, there are a series of fields to be supplied by the user who + * fills out the form. Each field has a name. Within a given form, the names are unique. + */ + FORM("form-data"); + + final String contentType; + + private Type(String contentType) { + this.contentType = contentType; + } + } + + /** Fluent API to build {@link Multipart} instances. */ + public static class Builder { + private final String boundary; + private final List parts = new ArrayList(); + private Type type = Type.MIXED; + + public Builder() { + this(UUID.randomUUID().toString()); + } + + public Builder(String boundary) { + this.boundary = boundary; + } + + /** Set the MIME type. */ + public Builder type(Type type) { + isNotNull(type, "Type must not be null."); + this.type = type; + return this; + } + + /** Add a part to the body. */ + public Builder addPart(Part part) { + isNotNull(part, "Part must not be null."); + parts.add(part); + return this; + } + + /** Assemble the specified parts into a {@link Multipart}. */ + public Multipart build() { + if (parts.isEmpty()) { + throw new IllegalStateException("Multipart body must have at least one part."); + } + return new Multipart(type, parts, boundary); + } + } + + private final List parts; + private final Map headers; + private final String boundary; + + private Multipart(Type type, List parts, String boundary) { + isNotNull(type, "Multipart type must not be null."); + + this.parts = parts; + this.headers = Collections.singletonMap( + "Content-Type", "multipart/" + type.contentType + "; boundary=" + boundary); + this.boundary = boundary; + } + + @Override public Map getHeaders() { + return headers; + } + + @Override public void writeBodyTo(OutputStream stream) throws IOException { + byte[] boundary = this.boundary.getBytes("UTF-8"); + boolean first = true; + for (Part part : parts) { + writeBoundary(stream, boundary, first, false); + writePart(stream, part); + first = false; + } + writeBoundary(stream, boundary, false, true); + } + + private static void writeBoundary(OutputStream out, byte[] boundary, boolean first, boolean last) + throws IOException { + if (!first) { + out.write('\r'); + out.write('\n'); + } + out.write('-'); + out.write('-'); + out.write(boundary); + if (last) { + out.write('-'); + out.write('-'); + } else { + out.write('\r'); + out.write('\n'); + } + } + + private static void writePart(OutputStream out, Part part) throws IOException { + Map headers = part.getHeaders(); + if (headers != null) { + for (Map.Entry header : headers.entrySet()) { + out.write(header.getKey().getBytes("UTF-8")); + out.write(':'); + out.write(' '); + out.write(header.getValue().getBytes("UTF-8")); + out.write('\r'); + out.write('\n'); + } + } + out.write('\r'); + out.write('\n'); + part.writeBodyTo(out); + } +} diff --git a/okhttp/src/main/java/com/squareup/okhttp/Part.java b/okhttp/src/main/java/com/squareup/okhttp/Part.java new file mode 100644 index 000000000..6225fe4d6 --- /dev/null +++ b/okhttp/src/main/java/com/squareup/okhttp/Part.java @@ -0,0 +1,260 @@ +// Copyright 2013 Square, Inc. +package com.squareup.okhttp; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.LinkedHashMap; +import java.util.Map; + +import static com.squareup.okhttp.Utils.copyStream; +import static com.squareup.okhttp.Utils.isNotEmpty; +import static com.squareup.okhttp.Utils.isNotNull; +import static com.squareup.okhttp.Utils.isNotZero; +import static com.squareup.okhttp.Utils.isNull; + +/** HTTP request data with associated headers. */ +public interface Part { + /** HTTP headers. */ + Map getHeaders(); + + /** + * Write request data to the specified stream. For best performance use a + * {@link java.io.BufferedOutputStream BufferedOutputStream}. + */ + void writeBodyTo(OutputStream stream) throws IOException; + + /** Fluent API to build {@link Part} instances. */ + public class Builder { + private static final int BUFFER_SIZE = 4096; + + private String headerType; + int headerLength; + private String headerLanguage; + private String headerEncoding; + private String headerDisposition; + private File bodyFile; + private InputStream bodyStream; + private byte[] bodyBytes; + private Multipart bodyMultipart; + private boolean hasBody = false; + + private void checkSetBody() { + if (hasBody) { + throw new IllegalStateException("Only one body per part."); + } + hasBody = true; + } + + /** Set the {@code Content-Type} header value. */ + public Builder contentType(String type) { + isNotEmpty(type, "Type must not be empty."); + isNull(headerType, "Type header already set."); + isNull(bodyMultipart, "Type cannot be set with multipart body."); + headerType = type; + return this; + } + + /** Set the {@code Content-Length} header value. */ + public Builder contentLength(int length) { + if (length <= 0) { + throw new IllegalStateException("Length must be greater than zero."); + } + isNotZero(headerLength, "Length header already set."); + headerLength = length; + return this; + } + + /** Set the {@code Content-Language} header value. */ + public Builder contentLanguage(String language) { + isNotEmpty(language, "Language must not be empty."); + isNull(headerLanguage, "Language header already set."); + headerLanguage = language; + return this; + } + + /** Set the {@code Content-Transfer-Encoding} header value. */ + public Builder contentEncoding(String encoding) { + isNotEmpty(encoding, "Encoding must not be empty."); + isNull(headerEncoding, "Encoding header already set."); + headerEncoding = encoding; + return this; + } + + /** Set the {@code Content-Disposition} header value. */ + public Builder contentDisposition(String disposition) { + isNotEmpty(disposition, "Disposition must not be empty."); + isNull(headerDisposition, "Disposition header already set."); + headerDisposition = disposition; + return this; + } + + /** Use the specified file as the body. */ + public Builder body(File body) { + isNotNull(body, "File body must not be null."); + checkSetBody(); + bodyFile = body; + return this; + } + + /** Use the specified stream as the body. */ + public Builder body(InputStream body) { + isNotNull(body, "Stream body must not be null."); + checkSetBody(); + bodyStream = body; + return this; + } + + /** Use the specified string as the body. */ + public Builder body(String body) { + isNotNull(body, "String body must not be null."); + checkSetBody(); + byte[] bytes; + try { + bytes = body.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("Unable to convert input to UTF-8: " + body, e); + } + bodyBytes = bytes; + headerLength = bytes.length; + return this; + } + + /** Use the specified bytes as the body. */ + public Builder body(byte[] body) { + isNotNull(body, "Byte array body must not be null."); + checkSetBody(); + bodyBytes = body; + headerLength = body.length; + return this; + } + + /** Use the specified {@link Multipart} as the body. */ + public Builder body(Multipart body) { + isNotNull(body, "Multipart body must not be null."); + if (headerType != null) { + throw new IllegalStateException( + "Content type must not be explicitly set for multipart body."); + } + checkSetBody(); + headerType = null; + bodyMultipart = body; + return this; + } + + /** Assemble the specified headers and body into a {@link Part}. */ + public Part build() { + Map headers = new LinkedHashMap(); + if (headerDisposition != null) { + headers.put("Content-Disposition", headerDisposition); + } + if (headerType != null) { + headers.put("Content-Type", headerType); + } + if (headerLength != 0) { + headers.put("Content-Length", Integer.toString(headerLength)); + } + if (headerLanguage != null) { + headers.put("Content-Language", headerLanguage); + } + if (headerEncoding != null) { + headers.put("Content-Transfer-Encoding", headerEncoding); + } + + if (bodyBytes != null) { + return new BytesPart(headers, bodyBytes); + } + if (bodyStream != null) { + return new StreamPart(headers, bodyStream); + } + if (bodyFile != null) { + return new FilePart(headers, bodyFile); + } + if (bodyMultipart != null) { + headers.putAll(bodyMultipart.getHeaders()); + return new PartPart(headers, bodyMultipart); + } + throw new IllegalStateException("Part required body to be set."); + } + + private abstract static class PartImpl implements Part { + private final Map headers; + + protected PartImpl(Map headers) { + this.headers = headers; + } + + @Override public Map getHeaders() { + return headers; + } + } + + private static final class PartPart extends PartImpl { + private final Part body; + + protected PartPart(Map headers, Part body) { + super(headers); + this.body = body; + } + + @Override public void writeBodyTo(OutputStream stream) throws IOException { + body.writeBodyTo(stream); + } + } + + static final class BytesPart extends PartImpl { + private final byte[] contents; + + BytesPart(Map headers, byte[] contents) { + super(headers); + this.contents = contents; + } + + @Override public void writeBodyTo(OutputStream out) throws IOException { + out.write(contents); + } + } + + private static final class StreamPart extends PartImpl { + private final InputStream in; + private final byte[] buffer = new byte[BUFFER_SIZE]; + + private StreamPart(Map headers, InputStream in) { + super(headers); + this.in = in; + } + + @Override public void writeBodyTo(OutputStream out) throws IOException { + copyStream(in, out, buffer); + } + } + + private static final class FilePart extends PartImpl { + private final File file; + private final byte[] buffer = new byte[BUFFER_SIZE]; + + private FilePart(Map headers, File file) { + super(headers); + this.file = file; + } + + @Override public void writeBodyTo(OutputStream out) throws IOException { + InputStream in = null; + try { + in = new FileInputStream(file); + copyStream(in, out, buffer); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ignored) { + } + } + } + } + } + } +} diff --git a/okhttp/src/main/java/com/squareup/okhttp/Utils.java b/okhttp/src/main/java/com/squareup/okhttp/Utils.java new file mode 100644 index 000000000..548f6a0be --- /dev/null +++ b/okhttp/src/main/java/com/squareup/okhttp/Utils.java @@ -0,0 +1,44 @@ +// Copyright 2013 Square, Inc. +package com.squareup.okhttp; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +final class Utils { + private Utils() { + // No instances. + } + + static void copyStream(InputStream in, OutputStream out, byte[] buffer) throws IOException { + int count; + while ((count = in.read(buffer)) != -1) { + out.write(buffer, 0, count); + } + } + + static void isNotNull(Object obj, String message) { + if (obj == null) { + throw new IllegalStateException(message); + } + } + + static void isNull(Object obj, String message) { + if (obj != null) { + throw new IllegalStateException(message); + } + } + + static void isNotEmpty(String thing, String message) { + isNotNull(thing, message); + if ("".equals(thing.trim())) { + throw new IllegalStateException(message); + } + } + + static void isNotZero(int value, String message) { + if (value != 0) { + throw new IllegalStateException(message); + } + } +}