Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 130 additions & 22 deletions src/main/java/com/microsoft/graph/models/extensions/Multipart.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import java.io.InputStream;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Map;

import com.google.common.annotations.VisibleForTesting;
import com.microsoft.graph.options.HeaderOption;

/**
Expand All @@ -20,6 +22,8 @@ public class Multipart {
private String boundary;
private static final String RETURN = "\r\n";
private ByteArrayOutputStream out;
public static final String MULTIPART_ENCODING = "US-ASCII";
private String contentType = "multipart/form-data";

/**
* Create a new multipart object
Expand All @@ -33,56 +37,160 @@ public Multipart() {
* Get the multipart boundary for use in the request header
* @return the multipart boundary
*/
public String boundary() {
public String getBoundary() {
return boundary;
}

/**
* Set the multipart boundary for use in the request header
* @param boundary The multipart boundary
*/
public void setBoundary(String boundary) {
this.boundary = boundary;
}

/**
* Get the contentType for use in the request header
* @return the multipart Content-Type
*/
public String getContentType() {
return contentType;
}

/**
* Set the contentType for use in the request header
* @param contentType The multipart Content-Type
*/
public void setContentType(String contentType) {
this.contentType = contentType;
}

/**
* Get the Content-Type header to send the multipart request
* @return the multipart header option
*/
public HeaderOption header() {
return new HeaderOption("Content-Type", "multipart/form-data; boundary=\"" + boundary + "\"");
return new HeaderOption("Content-Type", contentType + "; boundary=\"" + boundary + "\"");
}

private void writePartData(String partContent, byte[] byteArray) throws IOException{
out.write(partContent.getBytes(MULTIPART_ENCODING));
out.write(byteArray);
String returnContent = RETURN + RETURN;
out.write(returnContent.getBytes(MULTIPART_ENCODING));
}

/**
* Add a string part to the multipart body
* Create content headers value and parameter
* @param name The content header name
* @param contentType The content header Content-Type
* @param filename The content header filename
* @return content header value and parameter string
*/
@VisibleForTesting String createPartHeader(String name, String contentType, String filename) {
StringBuilder partContent = new StringBuilder(addBoundary());
partContent.append("Content-Disposition: form-data");
if(filename != null) {
if(name != null)
partContent.append("; name=\"").append(name).append("\"; filename=\"").append(filename).append("\"");
else
partContent.append("; filename=\"").append(filename).append("\"");
}
else if(name != null)
partContent.append("; name=\"").append(name).append("\"");
if(contentType != null)
partContent.append(RETURN).append("Content-Type: ").append(contentType);
partContent.append(RETURN).append(RETURN);
return partContent.toString();
}

/**
* Create content headers value and parameter
* @param contentValue The content header value
* @param contentDispParameter Map containing content paramter's key and value pair
* @return content header value and parameter string
*/
public static String createContentHeaderValue(String contentValue, Map<String, String> contentDispParameter) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just drop this method. There isn't any commonality between Content-Type and Content-Disposition values. We can build helper methods for creating header values that are semi-colon delimited parameter lists at a later point that can be used in other places. This method just creates confusion and we are not using it except for in tests.

Copy link
Copy Markdown
Contributor Author

@NakulSabharwal NakulSabharwal Sep 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The content header's value and parameter string building is common in Content-Type and Content-Disposition. eg. Content-Disposition:form-data; name="someName"
Content-Type: text/html; otherparams=paramValue
Also, if some other headers is being provided apart from these then also same helper function can be used.

String contentHeaderValue = contentValue;

if(contentDispParameter != null) {
for(Map.Entry<String,String> entry : contentDispParameter.entrySet())
contentHeaderValue += ";" + entry.getKey() + "=\"" + entry.getValue() + "\"";
}
return contentHeaderValue;
}

/**
* Create content headers header-name, value and parameter string
* @param headers Map containing Header-name and header-value pair
*/
private String createPartHeader(Map<String, String> headers) {
String partContent = addBoundary();
String defaultPartContent = "Content-Disposition: form-data;" + RETURN + "Content-Type: " + contentType + RETURN + RETURN;

if(headers != null) {
for(Map.Entry<String,String> entry : headers.entrySet())
partContent += entry.getKey() +": "+entry.getValue() + RETURN;
partContent += RETURN;
}
else
partContent += defaultPartContent;
return partContent;
}

/**
* Add multipart content headers and byte content
* @param name The multipart content name
* @param contentType The multipart Content-Type
* @param filename The multipart content file name
* @param byteArray The multipart byte content
* @throws IOException
*/
private void addData(String name, String contentType, String filename, byte[] byteArray) throws IOException {
String partContent = createPartHeader(name, contentType, filename);
writePartData(partContent, byteArray);
}

/**
* Add a part to the multipart body
* @param name The name of the part
* @param contentType The MIME type (text/html, text/plain, etc.)
* @param content The string content to include
* @param contentType The MIME type (text/html, video/mp4, etc.)
* @param byteArray The byte[] contents of the resource
* @throws IOException Throws an exception if the output stream cannot be written to
*/
public void addPart(String name, String contentType, String content) throws IOException {
addPart(name, contentType, content.getBytes());
public void addFormData(String name, String contentType, byte[] byteArray) throws IOException {
addData(name, contentType, null, byteArray);
}

/**
* Add a part to the multipart body
* @param name The name of the part
* @param contentType The MIME type (text/html, video/mp4, etc.)
* @param byteArray The byte[] contents of the resource
* @throws IOException Throws an exception if the output stream cannot be written to
*/
public void addPart(String name, String contentType, byte[] byteArray) throws IOException {
String partContent = addBoundary();
partContent +=
"Content-Disposition:form-data; name=\"" + name + "\"" + RETURN +
"Content-Type:" + contentType + RETURN +
RETURN;
out.write(partContent.getBytes());
out.write(byteArray);
String returnContent = RETURN + RETURN;
out.write(returnContent.getBytes());
public void addPart(String contentType, byte[] byteArray) throws IOException {
addData(null, contentType, null, byteArray);
}

/**
* Add a part to the multipart body
* @param headers Map containing Header's header-name(eg: Content-Disposition, Content-Type, etc..) and header's value-parameter string
* @param content The byte[] contents of the resource
* @throws IOException Throws an exception if the output stream cannot be written to
*/
public void addPart(Map<String, String> headers, byte[] content) throws IOException{
String partContent = createPartHeader(headers);
writePartData(partContent, content);
}

/**
* Add an HTML part to the multipart body
* @param name The name of the part
* @param content The HTML body for the part
* @throws IOException Throws an exception if the output stream cannot be written to
*/
public void addHtmlPart(String name, String content) throws IOException {
addPart(name, "text/html", content);
public void addHtmlPart(String name, byte[] content) throws IOException {
addFormData(name, "text/html", content);
}

/**
Expand All @@ -95,7 +203,7 @@ public void addHtmlPart(String name, String content) throws IOException {
public void addFilePart(String name, String contentType, java.io.File file) throws IOException {
InputStream fileStream = new FileInputStream(file);
byte[] fileBytes = getByteArray(fileStream);
addPart(name, contentType, fileBytes);
addData(name, contentType, file.getName(), fileBytes);
}

/**
Expand All @@ -121,7 +229,7 @@ private String addEnding() {
*/
public byte[] content() throws IOException {
ByteArrayOutputStream finalStream = out;
finalStream.write(addEnding().getBytes());
finalStream.write(addEnding().getBytes(MULTIPART_ENCODING));
return finalStream.toByteArray();
}

Expand Down
77 changes: 73 additions & 4 deletions src/test/java/com/microsoft/graph/functional/OneNoteTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.Before;
import org.junit.Ignore;
Expand All @@ -33,9 +37,12 @@
import com.microsoft.graph.requests.extensions.INotebookCollectionPage;
import com.microsoft.graph.requests.extensions.INotebookGetRecentNotebooksCollectionPage;
import com.microsoft.graph.requests.extensions.IOnenotePageCollectionPage;
import com.microsoft.graph.requests.extensions.IOnenotePageCollectionRequest;
import com.microsoft.graph.requests.extensions.IOnenotePageCollectionRequestBuilder;
import com.microsoft.graph.requests.extensions.IOnenoteRequestBuilder;
import com.microsoft.graph.requests.extensions.IOnenoteSectionCollectionPage;
import com.microsoft.graph.requests.extensions.ISectionGroupCollectionPage;
import com.microsoft.graph.requests.extensions.OnenotePageCollectionRequest;

/**
* Tests for OneNote API functionality
Expand All @@ -49,6 +56,7 @@ public class OneNoteTests {
private OnenotePage testPage;
private OnenoteSection testSection;
private SectionGroup testSectionGroup2;
private final String HTML_ENCODING= "US-ASCII";

@Before
public void setUp() {
Expand Down Expand Up @@ -288,10 +296,10 @@ public void testGetPreview() {

/**
* Test posting a page stream to a page
* @throws InterruptedException
* @throws InterruptedException, UnsupportedEncodingException
*/
@Test
public void testPostToNotebook() throws InterruptedException {
public void testPostToNotebook() throws InterruptedException, UnsupportedEncodingException {
SectionGroup sectionGroupData = new SectionGroup();

// Currently, there is no way to delete sections or section groups, so let's create a random one
Expand Down Expand Up @@ -319,7 +327,7 @@ public void testPostToNotebook() throws InterruptedException {
// Test HTML content
String content = "<html><head><title>Test Title</title></head><body>Test body</body></html>";

byte[] pageStream = content.getBytes();
byte[] pageStream = content.getBytes(HTML_ENCODING);
List<Option> options = new ArrayList<Option>();
options.add(new HeaderOption("Content-Type", "application/xhtml+xml"));
OnenotePage page = orb
Expand Down Expand Up @@ -410,7 +418,7 @@ public void testMultipartPost(){
File imgFile = new File("src/test/resources/hamilton.jpg");
File pdfFile = new File("src/test/resources/document.pdf");

multipart.addHtmlPart("Presentation", htmlContent);
multipart.addHtmlPart("Presentation", htmlContent.getBytes(HTML_ENCODING));
multipart.addFilePart("hamilton", "image/jpg", imgFile);
multipart.addFilePart("metadata", "application/pdf", pdfFile);

Expand All @@ -429,6 +437,67 @@ public void testMultipartPost(){
fail("Unable to write to output stream");
}
}

/**
* Test posting multipart content to a page
*/
@Test
public void testMultipartPostWithHeadersMap() throws Exception{
Multipart multipart = new Multipart();

String htmlContent = "<!DOCTYPE html>\r\n" +
"<html lang=\"en-US\">\r\n" +
"<head>\r\n" +
"<title>Test Multipart Page</title>\r\n" +
"<meta name=\"created\" content=\"2001-01-01T01:01+0100\">\r\n" +
"</head>\r\n" +
"<body>\r\n" +
"<p>\r\n" +
"<img src=\"name:image\" />\r\n" +
"</p>\r\n" +
"<p>\r\n" +
"<object data=\"name:attachment\" data-attachment=\"document.pdf\" /></p>\r\n" +
"\r\n" +
"</body>\r\n" +
"</html>";
File imgFile = new File("src/test/resources/hamilton.jpg");
File pdfFile = new File("src/test/resources/document.pdf");

Map<String, String> htmlHeaderMap = new HashMap<>();
Map<String, String> contentDispMap = new HashMap<>();
contentDispMap.put("name","Presentation" );
htmlHeaderMap.put("Content-Disposition", Multipart.createContentHeaderValue("form-data", contentDispMap));
htmlHeaderMap.put("Content-Type", Multipart.createContentHeaderValue("text/html", null));
multipart.addPart(htmlHeaderMap, htmlContent.getBytes(HTML_ENCODING));

InputStream fileStream = new FileInputStream(imgFile);
multipart.addFormData("hamilton", "image/jpg", getByteArray(fileStream));
multipart.addFilePart("metadata", "application/pdf", pdfFile);

// Add multipart request header
List<Option> options = new ArrayList<Option>();
options.add(multipart.header());

// Post the multipart content
IOnenotePageCollectionRequestBuilder pageReq = orb
.sections(testSection.id)
.pages();
String expectedRequestUrl = "https://graph.microsoft.com/v1.0/me/onenote/sections/"+testSection.id+"/pages";
assertEquals(expectedRequestUrl, pageReq.getRequestUrl());
IOnenotePageCollectionRequest request = pageReq.buildRequest(options);
assertNotNull(request);

OnenotePageCollectionRequest pageCollectionReq = (OnenotePageCollectionRequest)request;
List<HeaderOption> headeroption = pageCollectionReq.getHeaders();
assertEquals("Content-Type", headeroption.get(0).getName());

String expectedHeaderValue = "multipart/form-data; boundary=\""+multipart.getBoundary()+"\"";
assertEquals(expectedHeaderValue, headeroption.get(0).getValue().toString());
assertNotNull(multipart.content());

OnenotePage page = request.post(multipart.content());
assertNotNull(page);
}

/**
* Test patching a page's content
Expand Down
3 changes: 2 additions & 1 deletion src/test/java/com/microsoft/graph/http/MockConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class MockConnection implements IConnection {
private final ITestConnectionData mData;
private HashMap<String, String> mHeaders = new HashMap<>();
private Boolean mFollowRedirects;
private final String JSON_ENCODING = "UTF-8";

public MockConnection(ITestConnectionData data) {
mData = data;
Expand All @@ -40,7 +41,7 @@ public OutputStream getOutputStream() throws IOException {

@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(mData.getJsonResponse().getBytes());
return new ByteArrayInputStream(mData.getJsonResponse().getBytes(JSON_ENCODING));
}

@Override
Expand Down
Loading