Skip to content

Commit 918a316

Browse files
Merge pull request #131 from microsoftgraph/getbytes-encoding-fix
Getbytes encoding fix
2 parents 28cbf6e + 41c876d commit 918a316

File tree

4 files changed

+283
-27
lines changed

4 files changed

+283
-27
lines changed

src/main/java/com/microsoft/graph/models/extensions/Multipart.java

Lines changed: 130 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import java.io.InputStream;
77
import java.math.BigInteger;
88
import java.security.SecureRandom;
9+
import java.util.Map;
910

11+
import com.google.common.annotations.VisibleForTesting;
1012
import com.microsoft.graph.options.HeaderOption;
1113

1214
/**
@@ -20,6 +22,8 @@ public class Multipart {
2022
private String boundary;
2123
private static final String RETURN = "\r\n";
2224
private ByteArrayOutputStream out;
25+
public static final String MULTIPART_ENCODING = "US-ASCII";
26+
private String contentType = "multipart/form-data";
2327

2428
/**
2529
* Create a new multipart object
@@ -33,56 +37,160 @@ public Multipart() {
3337
* Get the multipart boundary for use in the request header
3438
* @return the multipart boundary
3539
*/
36-
public String boundary() {
40+
public String getBoundary() {
3741
return boundary;
3842
}
3943

44+
/**
45+
* Set the multipart boundary for use in the request header
46+
* @param boundary The multipart boundary
47+
*/
48+
public void setBoundary(String boundary) {
49+
this.boundary = boundary;
50+
}
51+
52+
/**
53+
* Get the contentType for use in the request header
54+
* @return the multipart Content-Type
55+
*/
56+
public String getContentType() {
57+
return contentType;
58+
}
59+
60+
/**
61+
* Set the contentType for use in the request header
62+
* @param contentType The multipart Content-Type
63+
*/
64+
public void setContentType(String contentType) {
65+
this.contentType = contentType;
66+
}
67+
4068
/**
4169
* Get the Content-Type header to send the multipart request
4270
* @return the multipart header option
4371
*/
4472
public HeaderOption header() {
45-
return new HeaderOption("Content-Type", "multipart/form-data; boundary=\"" + boundary + "\"");
73+
return new HeaderOption("Content-Type", contentType + "; boundary=\"" + boundary + "\"");
74+
}
75+
76+
private void writePartData(String partContent, byte[] byteArray) throws IOException{
77+
out.write(partContent.getBytes(MULTIPART_ENCODING));
78+
out.write(byteArray);
79+
String returnContent = RETURN + RETURN;
80+
out.write(returnContent.getBytes(MULTIPART_ENCODING));
4681
}
4782

4883
/**
49-
* Add a string part to the multipart body
84+
* Create content headers value and parameter
85+
* @param name The content header name
86+
* @param contentType The content header Content-Type
87+
* @param filename The content header filename
88+
* @return content header value and parameter string
89+
*/
90+
@VisibleForTesting String createPartHeader(String name, String contentType, String filename) {
91+
StringBuilder partContent = new StringBuilder(addBoundary());
92+
partContent.append("Content-Disposition: form-data");
93+
if(filename != null) {
94+
if(name != null)
95+
partContent.append("; name=\"").append(name).append("\"; filename=\"").append(filename).append("\"");
96+
else
97+
partContent.append("; filename=\"").append(filename).append("\"");
98+
}
99+
else if(name != null)
100+
partContent.append("; name=\"").append(name).append("\"");
101+
if(contentType != null)
102+
partContent.append(RETURN).append("Content-Type: ").append(contentType);
103+
partContent.append(RETURN).append(RETURN);
104+
return partContent.toString();
105+
}
106+
107+
/**
108+
* Create content headers value and parameter
109+
* @param contentValue The content header value
110+
* @param contentDispParameter Map containing content paramter's key and value pair
111+
* @return content header value and parameter string
112+
*/
113+
public static String createContentHeaderValue(String contentValue, Map<String, String> contentDispParameter) {
114+
String contentHeaderValue = contentValue;
115+
116+
if(contentDispParameter != null) {
117+
for(Map.Entry<String,String> entry : contentDispParameter.entrySet())
118+
contentHeaderValue += ";" + entry.getKey() + "=\"" + entry.getValue() + "\"";
119+
}
120+
return contentHeaderValue;
121+
}
122+
123+
/**
124+
* Create content headers header-name, value and parameter string
125+
* @param headers Map containing Header-name and header-value pair
126+
*/
127+
private String createPartHeader(Map<String, String> headers) {
128+
String partContent = addBoundary();
129+
String defaultPartContent = "Content-Disposition: form-data;" + RETURN + "Content-Type: " + contentType + RETURN + RETURN;
130+
131+
if(headers != null) {
132+
for(Map.Entry<String,String> entry : headers.entrySet())
133+
partContent += entry.getKey() +": "+entry.getValue() + RETURN;
134+
partContent += RETURN;
135+
}
136+
else
137+
partContent += defaultPartContent;
138+
return partContent;
139+
}
140+
141+
/**
142+
* Add multipart content headers and byte content
143+
* @param name The multipart content name
144+
* @param contentType The multipart Content-Type
145+
* @param filename The multipart content file name
146+
* @param byteArray The multipart byte content
147+
* @throws IOException
148+
*/
149+
private void addData(String name, String contentType, String filename, byte[] byteArray) throws IOException {
150+
String partContent = createPartHeader(name, contentType, filename);
151+
writePartData(partContent, byteArray);
152+
}
153+
154+
/**
155+
* Add a part to the multipart body
50156
* @param name The name of the part
51-
* @param contentType The MIME type (text/html, text/plain, etc.)
52-
* @param content The string content to include
157+
* @param contentType The MIME type (text/html, video/mp4, etc.)
158+
* @param byteArray The byte[] contents of the resource
53159
* @throws IOException Throws an exception if the output stream cannot be written to
54160
*/
55-
public void addPart(String name, String contentType, String content) throws IOException {
56-
addPart(name, contentType, content.getBytes());
161+
public void addFormData(String name, String contentType, byte[] byteArray) throws IOException {
162+
addData(name, contentType, null, byteArray);
57163
}
58164

59165
/**
60166
* Add a part to the multipart body
61-
* @param name The name of the part
62167
* @param contentType The MIME type (text/html, video/mp4, etc.)
63168
* @param byteArray The byte[] contents of the resource
64169
* @throws IOException Throws an exception if the output stream cannot be written to
65170
*/
66-
public void addPart(String name, String contentType, byte[] byteArray) throws IOException {
67-
String partContent = addBoundary();
68-
partContent +=
69-
"Content-Disposition:form-data; name=\"" + name + "\"" + RETURN +
70-
"Content-Type:" + contentType + RETURN +
71-
RETURN;
72-
out.write(partContent.getBytes());
73-
out.write(byteArray);
74-
String returnContent = RETURN + RETURN;
75-
out.write(returnContent.getBytes());
171+
public void addPart(String contentType, byte[] byteArray) throws IOException {
172+
addData(null, contentType, null, byteArray);
76173
}
77174

175+
/**
176+
* Add a part to the multipart body
177+
* @param headers Map containing Header's header-name(eg: Content-Disposition, Content-Type, etc..) and header's value-parameter string
178+
* @param content The byte[] contents of the resource
179+
* @throws IOException Throws an exception if the output stream cannot be written to
180+
*/
181+
public void addPart(Map<String, String> headers, byte[] content) throws IOException{
182+
String partContent = createPartHeader(headers);
183+
writePartData(partContent, content);
184+
}
185+
78186
/**
79187
* Add an HTML part to the multipart body
80188
* @param name The name of the part
81189
* @param content The HTML body for the part
82190
* @throws IOException Throws an exception if the output stream cannot be written to
83191
*/
84-
public void addHtmlPart(String name, String content) throws IOException {
85-
addPart(name, "text/html", content);
192+
public void addHtmlPart(String name, byte[] content) throws IOException {
193+
addFormData(name, "text/html", content);
86194
}
87195

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

101209
/**
@@ -121,7 +229,7 @@ private String addEnding() {
121229
*/
122230
public byte[] content() throws IOException {
123231
ByteArrayOutputStream finalStream = out;
124-
finalStream.write(addEnding().getBytes());
232+
finalStream.write(addEnding().getBytes(MULTIPART_ENCODING));
125233
return finalStream.toByteArray();
126234
}
127235

src/test/java/com/microsoft/graph/functional/OneNoteTests.java

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@
88
import java.io.BufferedReader;
99
import java.io.ByteArrayOutputStream;
1010
import java.io.File;
11+
import java.io.FileInputStream;
1112
import java.io.InputStream;
1213
import java.io.InputStreamReader;
14+
import java.io.UnsupportedEncodingException;
1315
import java.util.ArrayList;
16+
import java.util.HashMap;
1417
import java.util.List;
18+
import java.util.Map;
1519

1620
import org.junit.Before;
1721
import org.junit.Ignore;
@@ -33,9 +37,12 @@
3337
import com.microsoft.graph.requests.extensions.INotebookCollectionPage;
3438
import com.microsoft.graph.requests.extensions.INotebookGetRecentNotebooksCollectionPage;
3539
import com.microsoft.graph.requests.extensions.IOnenotePageCollectionPage;
40+
import com.microsoft.graph.requests.extensions.IOnenotePageCollectionRequest;
41+
import com.microsoft.graph.requests.extensions.IOnenotePageCollectionRequestBuilder;
3642
import com.microsoft.graph.requests.extensions.IOnenoteRequestBuilder;
3743
import com.microsoft.graph.requests.extensions.IOnenoteSectionCollectionPage;
3844
import com.microsoft.graph.requests.extensions.ISectionGroupCollectionPage;
45+
import com.microsoft.graph.requests.extensions.OnenotePageCollectionRequest;
3946

4047
/**
4148
* Tests for OneNote API functionality
@@ -49,6 +56,7 @@ public class OneNoteTests {
4956
private OnenotePage testPage;
5057
private OnenoteSection testSection;
5158
private SectionGroup testSectionGroup2;
59+
private final String HTML_ENCODING= "US-ASCII";
5260

5361
@Before
5462
public void setUp() {
@@ -288,10 +296,10 @@ public void testGetPreview() {
288296

289297
/**
290298
* Test posting a page stream to a page
291-
* @throws InterruptedException
299+
* @throws InterruptedException, UnsupportedEncodingException
292300
*/
293301
@Test
294-
public void testPostToNotebook() throws InterruptedException {
302+
public void testPostToNotebook() throws InterruptedException, UnsupportedEncodingException {
295303
SectionGroup sectionGroupData = new SectionGroup();
296304

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

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

413-
multipart.addHtmlPart("Presentation", htmlContent);
421+
multipart.addHtmlPart("Presentation", htmlContent.getBytes(HTML_ENCODING));
414422
multipart.addFilePart("hamilton", "image/jpg", imgFile);
415423
multipart.addFilePart("metadata", "application/pdf", pdfFile);
416424

@@ -429,6 +437,67 @@ public void testMultipartPost(){
429437
fail("Unable to write to output stream");
430438
}
431439
}
440+
441+
/**
442+
* Test posting multipart content to a page
443+
*/
444+
@Test
445+
public void testMultipartPostWithHeadersMap() throws Exception{
446+
Multipart multipart = new Multipart();
447+
448+
String htmlContent = "<!DOCTYPE html>\r\n" +
449+
"<html lang=\"en-US\">\r\n" +
450+
"<head>\r\n" +
451+
"<title>Test Multipart Page</title>\r\n" +
452+
"<meta name=\"created\" content=\"2001-01-01T01:01+0100\">\r\n" +
453+
"</head>\r\n" +
454+
"<body>\r\n" +
455+
"<p>\r\n" +
456+
"<img src=\"name:image\" />\r\n" +
457+
"</p>\r\n" +
458+
"<p>\r\n" +
459+
"<object data=\"name:attachment\" data-attachment=\"document.pdf\" /></p>\r\n" +
460+
"\r\n" +
461+
"</body>\r\n" +
462+
"</html>";
463+
File imgFile = new File("src/test/resources/hamilton.jpg");
464+
File pdfFile = new File("src/test/resources/document.pdf");
465+
466+
Map<String, String> htmlHeaderMap = new HashMap<>();
467+
Map<String, String> contentDispMap = new HashMap<>();
468+
contentDispMap.put("name","Presentation" );
469+
htmlHeaderMap.put("Content-Disposition", Multipart.createContentHeaderValue("form-data", contentDispMap));
470+
htmlHeaderMap.put("Content-Type", Multipart.createContentHeaderValue("text/html", null));
471+
multipart.addPart(htmlHeaderMap, htmlContent.getBytes(HTML_ENCODING));
472+
473+
InputStream fileStream = new FileInputStream(imgFile);
474+
multipart.addFormData("hamilton", "image/jpg", getByteArray(fileStream));
475+
multipart.addFilePart("metadata", "application/pdf", pdfFile);
476+
477+
// Add multipart request header
478+
List<Option> options = new ArrayList<Option>();
479+
options.add(multipart.header());
480+
481+
// Post the multipart content
482+
IOnenotePageCollectionRequestBuilder pageReq = orb
483+
.sections(testSection.id)
484+
.pages();
485+
String expectedRequestUrl = "https://graph.microsoft.com/v1.0/me/onenote/sections/"+testSection.id+"/pages";
486+
assertEquals(expectedRequestUrl, pageReq.getRequestUrl());
487+
IOnenotePageCollectionRequest request = pageReq.buildRequest(options);
488+
assertNotNull(request);
489+
490+
OnenotePageCollectionRequest pageCollectionReq = (OnenotePageCollectionRequest)request;
491+
List<HeaderOption> headeroption = pageCollectionReq.getHeaders();
492+
assertEquals("Content-Type", headeroption.get(0).getName());
493+
494+
String expectedHeaderValue = "multipart/form-data; boundary=\""+multipart.getBoundary()+"\"";
495+
assertEquals(expectedHeaderValue, headeroption.get(0).getValue().toString());
496+
assertNotNull(multipart.content());
497+
498+
OnenotePage page = request.post(multipart.content());
499+
assertNotNull(page);
500+
}
432501

433502
/**
434503
* Test patching a page's content

src/test/java/com/microsoft/graph/http/MockConnection.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class MockConnection implements IConnection {
1818
private final ITestConnectionData mData;
1919
private HashMap<String, String> mHeaders = new HashMap<>();
2020
private Boolean mFollowRedirects;
21+
private final String JSON_ENCODING = "UTF-8";
2122

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

4142
@Override
4243
public InputStream getInputStream() throws IOException {
43-
return new ByteArrayInputStream(mData.getJsonResponse().getBytes());
44+
return new ByteArrayInputStream(mData.getJsonResponse().getBytes(JSON_ENCODING));
4445
}
4546

4647
@Override

0 commit comments

Comments
 (0)