/** * Copyright (c) 2016 Couchbase, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions * and limitations under the License. */ package com.couchbase.lite; import com.couchbase.lite.support.CustomByteArrayOutputStream; import com.couchbase.lite.support.MultipartReaderDelegate; import com.couchbase.lite.support.Range; import junit.framework.Assert; import java.io.ByteArrayInputStream; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class MultipartReaderTest extends LiteTestCase { class TestMultipartReaderDelegate implements MultipartReaderDelegate { private CustomByteArrayOutputStream currentPartData; private List<Map<String, String>> headersList; private List<CustomByteArrayOutputStream> partList; public void startedPart(Map<String, String> headers) { Assert.assertNull(currentPartData); if (partList == null) partList = new ArrayList<CustomByteArrayOutputStream>(); currentPartData = new CustomByteArrayOutputStream(1024); partList.add(currentPartData); if (headersList == null) headersList = new ArrayList<Map<String, String>>(); headersList.add(headers); } public void appendToPart(byte[] data) { appendToPart(data, 0, data.length); } public void appendToPart(final byte[] data, int off, int len) { Assert.assertNotNull(currentPartData); currentPartData.write(data, off, len); } public void finishedPart() { Assert.assertNotNull(currentPartData); currentPartData = null; } } public void testParseContentType() { Charset utf8 = Charset.forName("UTF-8"); HashMap<String, byte[]> contentTypes = new HashMap<String, byte[]>(); contentTypes.put("multipart/related; boundary=\"BOUNDARY\"", new String("\r\n--BOUNDARY").getBytes(utf8)); contentTypes.put("multipart/related; boundary=BOUNDARY", new String("\r\n--BOUNDARY").getBytes(utf8)); contentTypes.put("multipart/related;boundary=X", new String("\r\n--X").getBytes(utf8)); for (String contentType : contentTypes.keySet()) { MultipartReaderDelegate delegate = null; com.couchbase.lite.support.MultipartReader reader = new com.couchbase.lite.support.MultipartReader(contentType, delegate); byte[] expectedBoundary = (byte[]) contentTypes.get(contentType); byte[] boundary = reader.getBoundary(); Assert.assertTrue(Arrays.equals(boundary, expectedBoundary)); } try { MultipartReaderDelegate delegate = null; com.couchbase.lite.support.MultipartReader reader = new com.couchbase.lite.support.MultipartReader("multipart/related; boundary=\"BOUNDARY", delegate); Assert.assertTrue("Should not have gotten here, above lines should have thrown exception", false); } catch (Exception e) { // expected exception } } public void testParseHeaders() { String testString = new String("\r\nFoo: Bar\r\n Header : Val ue "); com.couchbase.lite.support.MultipartReader reader = new com.couchbase.lite.support.MultipartReader("multipart/related;boundary=X", null); reader.parseHeaders(testString); Assert.assertEquals(reader.headers.keySet().size(), 2); } public void testSearchFor() throws Exception { String testString = new String("\r\n\r\n"); byte[] testStringBytes = testString.getBytes(Charset.forName("UTF-8")); com.couchbase.lite.support.MultipartReader reader = new com.couchbase.lite.support.MultipartReader("multipart/related;boundary=X", null); reader.appendData(testStringBytes); Range r = reader.searchFor(testStringBytes, 0); Assert.assertEquals(0, r.getLocation()); Assert.assertEquals(4, r.getLength()); Range r2 = reader.searchFor(new String("nomatch").getBytes(Charset.forName("UTF-8")), 0); Assert.assertEquals(-1, r2.getLocation()); Assert.assertEquals(0, r2.getLength()); } public void testReaderOperation() { Charset utf8 = Charset.forName("UTF-8"); byte[] mime = new String("--BOUNDARY\r\nFoo: Bar\r\n Header : Val ue \r\n\r\npart the first\r\n--BOUNDARY \r\n\r\n2nd part\r\n--BOUNDARY--").getBytes(utf8); readerOperationWithMime(mime, "part the first", "2nd part", mime.length); byte[] mime2 = new String("--BOUNDARY\r\nFoo: Bar\r\n Header : Val ue \r\n\r\npart the first\r\n--BOUNDARY\r\n\r\n2nd part\r\n--BOUNDARY--").getBytes(utf8); readerOperationWithMime(mime2, "part the first", "2nd part", mime2.length); StringBuffer mime3Buffer = new StringBuffer(); StringBuffer mime3BufferFirstPart = new StringBuffer(); mime3Buffer.append("--BOUNDARY\r\nFoo: Bar\r\n Header : Val ue \r\n\r\n"); for (int i = 0; i < 10000; i++) { mime3BufferFirstPart.append("large_part_data"); } mime3Buffer.append(mime3BufferFirstPart); mime3Buffer.append("\r\n--BOUNDARY\r\n\r\n2nd part\r\n--BOUNDARY--"); byte[] mime3 = mime3Buffer.toString().getBytes(utf8); readerOperationWithMime(mime3, mime3BufferFirstPart.toString(), "2nd part", 1024); } private void readerOperationWithMime(byte[] mime, String part1ExpectedStr, String part2ExpectedStr, int recommendedChunkSize) { Charset utf8 = Charset.forName("UTF-8"); // if the caller passes in a special chunksize, which is not equal to mime.length, then // lets test the algorithm _only_ at that chunksize. otherwise, test it at every chunksize // between 1 and mime.length. (this is needed because when testing with a very large mime value, // the test takes too long to test at every single chunk size) int chunkSize = 1; if (recommendedChunkSize != mime.length) { chunkSize = recommendedChunkSize; } for (; chunkSize <= recommendedChunkSize; ++chunkSize) { ByteArrayInputStream mimeInputStream = new ByteArrayInputStream(mime); TestMultipartReaderDelegate delegate = new TestMultipartReaderDelegate(); String contentType = "multipart/related; boundary=\"BOUNDARY\""; com.couchbase.lite.support.MultipartReader reader = new com.couchbase.lite.support.MultipartReader(contentType, delegate); Assert.assertFalse(reader.finished()); int location = 0; int length = 0; do { Assert.assertTrue("Parser didn't stop at end", location < mime.length); length = Math.min(chunkSize, (mime.length - location)); byte[] bytesRead = new byte[length]; mimeInputStream.read(bytesRead, 0, length); reader.appendData(bytesRead); location += chunkSize; } while (!reader.finished()); Assert.assertEquals(delegate.partList.size(), 2); Assert.assertEquals(delegate.headersList.size(), 2); byte[] part1Expected = part1ExpectedStr.getBytes(utf8); byte[] part2Expected = part2ExpectedStr.getBytes(utf8); CustomByteArrayOutputStream part1 = delegate.partList.get(0); CustomByteArrayOutputStream part2 = delegate.partList.get(1); Assert.assertTrue(Arrays.equals(part1.toByteArray(), part1Expected)); Assert.assertTrue(Arrays.equals(part2.toByteArray(), part2Expected)); Map<String, String> headers1 = delegate.headersList.get(0); Assert.assertTrue(headers1.containsKey("Foo")); Assert.assertEquals(headers1.get("Foo"), "Bar"); Assert.assertTrue(headers1.containsKey("Header")); Assert.assertEquals(headers1.get("Header"), "Val ue"); } } }