1
0
mirror of https://github.com/square/okhttp.git synced 2025-11-29 06:23:09 +03:00

Import mimecraft as-is.

This commit is contained in:
Jesse Wilson
2014-06-07 09:44:56 -07:00
parent 349251ffb6
commit 6a2242ce30
10 changed files with 891 additions and 0 deletions

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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<String, String> expectedHeaders = new LinkedHashMap<String, String>();
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());
}
}

View File

@@ -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<String, String> getHeaders() {
return Collections.emptyMap();
}
@Override public void writeBodyTo(OutputStream out) throws IOException {
out.write(content);
}
}

View File

@@ -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
}
}

View File

@@ -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;
/**
* <a href="http://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2.1">HTML 2.0</a>-compliant
* form data.
*/
public final class FormEncoding implements Part {
private static final Map<String, String> 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<String, String> getHeaders() {
return HEADERS;
}
@Override public void writeBodyTo(OutputStream stream) throws IOException {
stream.write(data);
}
}

View File

@@ -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;
/** <a href="http://www.ietf.org/rfc/rfc2387.txt">RFC 2387</a>-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<Part> parts = new ArrayList<Part>();
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<Part> parts;
private final Map<String, String> headers;
private final String boundary;
private Multipart(Type type, List<Part> 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<String, String> 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<String, String> headers = part.getHeaders();
if (headers != null) {
for (Map.Entry<String, String> 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);
}
}

View File

@@ -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<String, String> 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<String, String> headers = new LinkedHashMap<String, String>();
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<String, String> headers;
protected PartImpl(Map<String, String> headers) {
this.headers = headers;
}
@Override public Map<String, String> getHeaders() {
return headers;
}
}
private static final class PartPart extends PartImpl {
private final Part body;
protected PartPart(Map<String, String> 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<String, String> 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<String, String> 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<String, String> 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) {
}
}
}
}
}
}
}

View File

@@ -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);
}
}
}