diff --git a/WordPressComRest/build.gradle b/WordPressComRest/build.gradle index e718c1d..fdd7677 100644 --- a/WordPressComRest/build.gradle +++ b/WordPressComRest/build.gradle @@ -105,6 +105,12 @@ afterEvaluate { name 'Maxime Biais' email 'maxime@automattic.com' } + + developer { + id 'hypest' + name 'Stefanos Togoulidis' + email 'stefanos.togoulidis@automattic.com' + } } } } diff --git a/WordPressComRest/src/androidTest/java/src/com/wordpress/rest/RestRequestTest.java b/WordPressComRest/src/androidTest/java/src/com/wordpress/rest/RestRequestTest.java new file mode 100644 index 0000000..d3e6496 --- /dev/null +++ b/WordPressComRest/src/androidTest/java/src/com/wordpress/rest/RestRequestTest.java @@ -0,0 +1,89 @@ +package com.wordpress.rest; + +import com.android.volley.AuthFailureError; +import com.android.volley.Request; + +import android.net.Uri; +import android.os.Debug; +import android.test.AndroidTestCase; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; + +public class RestRequestTest extends AndroidTestCase { + private File mTempFile; + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + + if (mTempFile != null) { + mTempFile.delete(); + } + } + + public void testMultiPartBodyAndContentType() throws AuthFailureError { + final String bodyStr = "request body"; + final String bodyContentType = "multipart/form-data; boundary=---------------------1234"; + + RestRequest restRequest = new RestRequest(Request.Method.POST, "http://url", bodyStr.getBytes(), + bodyContentType, null, null); + assertEquals(bodyStr, new String(restRequest.getBody())); + assertEquals(bodyContentType, restRequest.getBodyContentType()); + } + + public void testMultiPartBuilderMimeType() throws AuthFailureError, IOException { + MultipartRequestBuilder mpb = new MultipartRequestBuilder(); + final String boundary = mpb.getBoundary(); + + RestRequest request = mpb.build("http://url"); + assertEquals("multipart/form-data; boundary=" + boundary, request.getBodyContentType()); + } + + public void testMultiPartBuilderText() throws AuthFailureError, IOException { + MultipartRequestBuilder mpb = new MultipartRequestBuilder(); + + final String simpleText = "simple text"; + mpb.addPart(simpleText); + + RestRequest restRequest = mpb.build("http://url"); + + // See "7.2.1 Multipart: The common syntax" at https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html + final String encapsulationBoundary = "--" + mpb.getBoundary(); + + final String lineEnd = "\r\n"; + assertEquals(encapsulationBoundary + lineEnd + simpleText + lineEnd, new String(restRequest.getBody())); + } + + public void testMultiPartBuilderFile() throws AuthFailureError, IOException { + final byte[] bytes = new byte[] {5, 6, 7, 8, 9, 10}; + + mTempFile = new File(getContext().getCacheDir(), "tempFile.jpg"); + FileOutputStream fos = new FileOutputStream(mTempFile); + fos.write(bytes); + fos.flush(); + fos.close(); + + MultipartRequestBuilder mpb = new MultipartRequestBuilder(); + mpb.addPart("data", mTempFile); + RestRequest restRequest = mpb.build("http://url"); + + // See "7.2.1 Multipart: The common syntax" at https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html + final String encapsulationBoundary = "--" + mpb.getBoundary(); + + final String lineEnd = "\r\n"; + + String expected = + encapsulationBoundary + lineEnd + + "Content-Disposition: form-data; name=\"data\"; filename=\"" + mTempFile.getName() + "\"" + lineEnd + + lineEnd + + new String(bytes) + + lineEnd + + encapsulationBoundary + + "--" + lineEnd; + + assertEquals(expected, new String(restRequest.getBody())); + } +} diff --git a/WordPressComRest/src/main/java/com/wordpress/rest/MultipartRequestBuilder.java b/WordPressComRest/src/main/java/com/wordpress/rest/MultipartRequestBuilder.java new file mode 100644 index 0000000..0b17c7f --- /dev/null +++ b/WordPressComRest/src/main/java/com/wordpress/rest/MultipartRequestBuilder.java @@ -0,0 +1,92 @@ +package com.wordpress.rest; + +import com.android.volley.Request; +import com.android.volley.Response; + +import org.json.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class MultipartRequestBuilder { + + private final String TWO_HYPHENS = "--"; + private final String LINE_END = "\r\n"; + + private String mBoundary = "-------------" + System.currentTimeMillis(); + private String mMimeType = "multipart/form-data; boundary=" + mBoundary; + + private ByteArrayOutputStream mByteArrayOutputStream; + private DataOutputStream mDataOutputStream; + + private Response.Listener mListener; + private Response.ErrorListener mErrorListener; + + public MultipartRequestBuilder() { + mByteArrayOutputStream = new ByteArrayOutputStream(); + mDataOutputStream = new DataOutputStream(mByteArrayOutputStream); + } + + public String getBoundary() { + return mBoundary; + } + + public String getMimeType() { + return mMimeType; + } + + public MultipartRequestBuilder setResponseListener(Response.Listener listener) { + mListener = listener; + return this; + } + + public MultipartRequestBuilder setResponseErrorListener(Response.ErrorListener errorListener) { + mErrorListener = errorListener; + return this; + } + + public MultipartRequestBuilder addPart(String textData) throws IOException { + mDataOutputStream.writeBytes(TWO_HYPHENS + mBoundary + LINE_END); + mDataOutputStream.writeBytes(textData); + mDataOutputStream.writeBytes(LINE_END); + return this; + } + + public MultipartRequestBuilder addPart(String name, File file) throws IOException { + mDataOutputStream.writeBytes(TWO_HYPHENS + mBoundary + LINE_END); + mDataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + + file.getName() + "\"" + LINE_END); + // dataOutputStream.writeBytes("Content-Type: image/jpeg" + LINE_END); + mDataOutputStream.writeBytes(LINE_END); + + InputStream inputStream = new FileInputStream(file); + byte[] buffer = new byte[1024]; + int bytesRead = 0; + + while ((bytesRead = inputStream.read(buffer)) != -1) { + mDataOutputStream.write(buffer, 0, bytesRead); + } + + mDataOutputStream.writeBytes(LINE_END); + mDataOutputStream.writeBytes(TWO_HYPHENS + mBoundary + TWO_HYPHENS + LINE_END); + return this; + } + + public MultipartRequestBuilder addPart(String parameterName, String parameterValue) throws IOException { + mDataOutputStream.writeBytes(TWO_HYPHENS + mBoundary + LINE_END); + mDataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" + parameterName + "\"" + LINE_END); + mDataOutputStream.writeBytes("Content-Type: text/plain; charset=UTF-8" + LINE_END); + mDataOutputStream.writeBytes(LINE_END); + mDataOutputStream.writeBytes(parameterValue + LINE_END); + return this; + } + + public RestRequest build(String url) { + return new RestRequest(Request.Method.POST, url, mByteArrayOutputStream.toByteArray(), mMimeType, mListener, + mErrorListener); + } +} \ No newline at end of file diff --git a/WordPressComRest/src/main/java/com/wordpress/rest/RestRequest.java b/WordPressComRest/src/main/java/com/wordpress/rest/RestRequest.java index 19fb405..1014ef0 100644 --- a/WordPressComRest/src/main/java/com/wordpress/rest/RestRequest.java +++ b/WordPressComRest/src/main/java/com/wordpress/rest/RestRequest.java @@ -1,5 +1,6 @@ package com.wordpress.rest; +import com.android.volley.AuthFailureError; import com.android.volley.NetworkResponse; import com.android.volley.ParseError; import com.android.volley.Request; @@ -36,11 +37,43 @@ public interface OnAuthFailedListener { private final Map mParams; private final Map mHeaders = new HashMap(2); + private final byte[] mMultipartBody; + private final String mBodyContentType; + + /** + * Prepare a REST request based on URL-encoded form data + * @param method HTTP method to execute + * @param url the URL to execute the request against + * @param params the map of form data pairs to encode + * @param listener the listener for successful completion + * @param errorListener the listener for failed completion + */ public RestRequest(int method, String url, Map params, com.android.volley.Response.Listener listener, com.android.volley.Response.ErrorListener errorListener) { super(method, url, errorListener); mParams = params; + mMultipartBody = null; + mBodyContentType = null; + mListener = listener; + } + + /** + * Prepare a REST request based on MultiPart form data + * @param method HTTP method to execute + * @param url the URL to execute the request against + * @param multipartBody the byte array of the body + * @param bodyContentType the body content type including the parts boundary string + * @param listener the listener for successful completion + * @param errorListener the listener for failed completion + */ + public RestRequest(int method, String url, byte[] multipartBody, String bodyContentType, + com.android.volley.Response.Listener listener, + com.android.volley.Response.ErrorListener errorListener) { + super(method, url, errorListener); + mParams = null; + mMultipartBody = multipartBody; + mBodyContentType = bodyContentType; mListener = listener; } @@ -64,6 +97,16 @@ public void setOnAuthFailedListener(OnAuthFailedListener onAuthFailedListener) { mOnAuthFailedListener = onAuthFailedListener; } + @Override + public byte[] getBody() throws AuthFailureError { + return (mMultipartBody == null ? super.getBody() : mMultipartBody); + } + + @Override + public String getBodyContentType() { + return (mBodyContentType == null ? super.getBodyContentType() : mBodyContentType); + } + @Override public Map getHeaders() { return mHeaders;