| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.chemistry.opencmis.server.impl; |
| |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Random; |
| |
| import javax.servlet.http.HttpServletRequest; |
| |
| import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; |
| import org.apache.chemistry.opencmis.commons.impl.IOUtils; |
| import org.apache.chemistry.opencmis.server.impl.browser.MultipartParser; |
| import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory; |
| import org.junit.Test; |
| |
| /** |
| * Tests the multipart parser. |
| */ |
| public class MultipartParserTest { |
| |
| private static final int THRESHOLD = 4 * 1024 * 1024; |
| private static final int MAX_SIZE = -1; |
| |
| @Test |
| public void testMultipartParser() throws Exception { |
| String boundary = "---- next ----"; |
| byte[] content = "This is content!".getBytes(); |
| byte[] formdata = ("\r\n--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"field1\"\r\n" |
| + "\r\n" + "value1\r\n" + "--" + boundary + "\r\n" |
| + "content-disposition: form-data; name=\"field2\"\r\n" + "\r\n" + "value2\r\n" + "--" + boundary |
| + "\r\n" + "content-disposition: form-data; name=\"field3\"\r\n" + "\r\n" + "value3\r\n" + "--" |
| + boundary + "\r\n" |
| + "Content-Disposition: form-data; name=\"content\"; filename=test-filename.txt\r\n" |
| + "Content-Type: text/plain\r\n" + "Content-Transfer-Encoding: binary\r\n" + "\r\n" |
| + new String(content) + "\r\n" + "--" + boundary + "--").getBytes("ISO-8859-1"); |
| |
| MultipartParser parser = prepareParser(boundary, formdata); |
| |
| Map<String, String> values = new HashMap<String, String>(); |
| values.put("field1", "value1"); |
| values.put("field2", "value2"); |
| values.put("field3", "value3"); |
| |
| assertMultipartBasics(parser, 4, values, true, "test-filename.txt", "text/plain", content); |
| } |
| |
| @Test |
| public void testMultipartParser2() throws Exception { |
| String boundary = "-----------------------------1294919323195"; |
| byte[] content = "Test content!".getBytes("ISO-8859-1"); |
| byte[] formdata = ("\r\n--" |
| + boundary |
| + "\r\nContent-Disposition: form-data; name=\"fileUploader\"; filename=\"Ä.txt\"\r\nContent-Type: text/plain\r\n\r\n" |
| + new String(content) + "\r\n--" + boundary |
| + "\r\nContent-Disposition: form-data; name=\"fileUploader-data\"\r\n\r\n\r\n--" + boundary |
| + "\r\nContent-Disposition: form-data; name=\"objectid\"\r\n\r\nf6bad54b4696bf2ac9249805\r\n--" |
| + boundary + "\r\nContent-Disposition: form-data; name=\"cmisaction\"\r\n\r\ncreateDocument\r\n--" |
| + boundary + "\r\nContent-Disposition: form-data; name=\"propertyId[0]\"\r\n\r\ncmis:name\r\n--" |
| + boundary + "\r\nContent-Disposition: form-data; name=\"propertyValue[0]\"\r\n\r\nÄ.txt\r\n--" |
| + boundary |
| + "\r\nContent-Disposition: form-data; name=\"propertyId[1]\"\r\n\r\ncmis:objectTypeId\r\n--" |
| + boundary + "\r\nContent-Disposition: form-data; name=\"propertyValue[1]\"\r\n\r\ncmis:document\r\n--" |
| + boundary |
| + "\r\nContent-Disposition: form-data; name=\"token\"\r\n\r\n855475d8a6169b5f57111f5921f56136\r\n--" |
| + boundary + "--").getBytes("ISO-8859-1"); |
| |
| MultipartParser parser = prepareParser(boundary, formdata); |
| |
| Map<String, String> values = new HashMap<String, String>(); |
| values.put("fileUploader-data", ""); |
| values.put("objectid", "f6bad54b4696bf2ac9249805"); |
| values.put("cmisaction", "createDocument"); |
| values.put("propertyId[0]", "cmis:name"); |
| values.put("propertyValue[0]", "Ä.txt"); |
| values.put("propertyId[1]", "cmis:objectTypeId"); |
| values.put("propertyValue[1]", "cmis:document"); |
| values.put("token", "855475d8a6169b5f57111f5921f56136"); |
| |
| assertMultipartBasics(parser, 9, values, true, "Ä.txt", "text/plain", content); |
| } |
| |
| @Test |
| public void testNoPreamble() throws Exception { |
| String boundary = "BoUnDaRy--987654320"; |
| byte[] formdata = ("--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"field1\"\r\n" + "\r\n" |
| + "value1\r\n" + "--" + boundary + "--").getBytes(); |
| |
| MultipartParser parser = prepareParser(boundary, formdata); |
| |
| Map<String, String> values = new HashMap<String, String>(); |
| values.put("field1", "value1"); |
| |
| assertMultipartBasics(parser, 1, values, false, null, null, null); |
| } |
| |
| @Test |
| public void testPreamble() throws Exception { |
| String boundary = "BoUnDaRy--987654320"; |
| byte[] formdata = ("This is a preamble.\r\n--" + boundary + "\r\n" |
| + "Content-Disposition: form-data; name=\"field1\"\r\n" + "\r\n" + "value1\r\n" + "--" + boundary + "--") |
| .getBytes(); |
| |
| MultipartParser parser = prepareParser(boundary, formdata); |
| |
| Map<String, String> values = new HashMap<String, String>(); |
| values.put("field1", "value1"); |
| |
| assertMultipartBasics(parser, 1, values, false, null, null, null); |
| } |
| |
| @Test |
| public void testEpilogue() throws Exception { |
| String boundary = "BoUnDaRy--987654320"; |
| byte[] formdata = ("\r\n--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"field1\"\r\n" |
| + "\r\n" + "value1\r\n" + "--" + boundary + "--This is an epilogue.").getBytes(); |
| |
| MultipartParser parser = prepareParser(boundary, formdata); |
| |
| Map<String, String> values = new HashMap<String, String>(); |
| values.put("field1", "value1"); |
| |
| assertMultipartBasics(parser, 1, values, false, null, null, null); |
| } |
| |
| @Test |
| public void testEmpty() throws Exception { |
| String boundary = "BoUnDaRy--987654320"; |
| byte[] formdata = ("\r\n--" + boundary + "--").getBytes(); |
| |
| MultipartParser parser = prepareParser(boundary, formdata); |
| |
| Map<String, String> values = new HashMap<String, String>(); |
| |
| assertMultipartBasics(parser, 0, values, false, null, null, null); |
| } |
| |
| @Test |
| public void testContentOnly() throws Exception { |
| String boundary = "ABCD-1234"; |
| byte[] content = "abcäöü".getBytes(); |
| byte[] formdata = ("\r\n--" + boundary + "\r\n" |
| + "Content-Disposition: form-data; name=\"content\"; filename=\"a new file\"\r\n" |
| + "Content-Type: application/something\r\n" + "Content-Transfer-Encoding: binary\r\n" + "\r\n" |
| + new String(content) + "\r\n" + "--" + boundary + "--").getBytes(); |
| |
| MultipartParser parser = prepareParser(boundary, formdata); |
| |
| Map<String, String> values = new HashMap<String, String>(); |
| |
| assertMultipartBasics(parser, 1, values, true, "a new file", "application/something", content); |
| } |
| |
| @Test |
| public void testBigContent() throws Exception { |
| String boundary = "---- next ----"; |
| byte[] content = new byte[2 * 1024 * 1024]; |
| |
| Random rnd = new Random(); |
| for (int i = 0; i < content.length; i++) { |
| content[i] = (byte) ('a' + rnd.nextInt('z' - 'a')); |
| } |
| |
| byte[] formdata = ("\r\n--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"field1\"\r\n" |
| + "\r\n" + "value1\r\n" + "--" + boundary + "\r\n" |
| + "content-disposition: form-data; name=\"field2\"\r\n" + "\r\n" + "value2\r\n" + "--" + boundary |
| + "\r\n" + "content-disposition: form-data; name=\"field3\"\r\n" + "\r\n" + "value3\r\n" + "--" |
| + boundary + "\r\n" + "Content-Disposition: form-data; name=\"content\"; filename=bigtest.txt\r\n" |
| + "Content-Type: text/plain\r\n" + "Content-Transfer-Encoding: binary\r\n" + "\r\n" |
| + new String(content) + "\r\n" + "--" + boundary + "--").getBytes(); |
| |
| MultipartParser parser = prepareParser(boundary, formdata); |
| |
| Map<String, String> values = new HashMap<String, String>(); |
| values.put("field1", "value1"); |
| values.put("field2", "value2"); |
| values.put("field3", "value3"); |
| |
| assertMultipartBasics(parser, 4, values, true, "bigtest.txt", "text/plain", content); |
| } |
| |
| @Test |
| public void testManyFields() throws Exception { |
| String boundary = "============"; |
| |
| StringBuilder sb = new StringBuilder("\r\n"); |
| |
| Map<String, String> values = new HashMap<String, String>(); |
| |
| for (int i = 0; i < 10000; i++) { |
| String name = "field" + i; |
| String value = "value " + i * i; |
| |
| values.put(name, value); |
| |
| sb.append("\r\n--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"" + name + "\"\r\n" |
| + "\r\n" + value); |
| } |
| |
| sb.append("\r\n" + "--" + boundary + "--"); |
| |
| MultipartParser parser = prepareParser(boundary, sb.toString().getBytes(IOUtils.ISO_8859_1)); |
| |
| assertMultipartBasics(parser, values.size(), values, false, null, null, null); |
| } |
| |
| @Test |
| public void testLargeFields() throws Exception { |
| testLargeFields(64 * 1024); |
| testLargeFields(128 * 1024); |
| testLargeFields(256 * 1024); |
| testLargeFields(512 * 1024); |
| testLargeFields(1024 * 1024); |
| } |
| |
| private void testLargeFields(int size) throws Exception { |
| String boundary = "============"; |
| |
| StringBuilder sb = new StringBuilder("\r\n"); |
| |
| Random rnd = new Random(); |
| Map<String, String> values = new HashMap<String, String>(); |
| |
| for (int i = 0; i < 5; i++) { |
| String name = "field" + i; |
| |
| StringBuilder valueBuffer = new StringBuilder(); |
| for (int j = 0; j < size; j++) { |
| valueBuffer.append((char) ('a' + rnd.nextInt(26))); |
| } |
| String value = valueBuffer.toString(); |
| |
| values.put(name, value); |
| |
| sb.append("\r\n--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n"); |
| sb.append(value); |
| } |
| |
| sb.append("\r\n" + "--" + boundary + "--"); |
| |
| MultipartParser parser = prepareParser(boundary, sb.toString().getBytes(IOUtils.ISO_8859_1)); |
| |
| assertMultipartBasics(parser, values.size(), values, false, null, null, null); |
| } |
| |
| @Test |
| public void testCharsetsInContentType() throws Exception { |
| String[] charsets = new String[] { "utf-8", "iso-8859-1", "utf-16" }; |
| |
| String boundary = "ldchqeriuvoqeirbvxipu eckqnqklwjcnqwklcqwncqewlciqecqwecevoipooei cqwcoewcq"; |
| StringBuilder value = new StringBuilder(); |
| |
| for (int i = 1; i < 255; i++) { |
| value.append((char) i); |
| } |
| |
| Map<String, String> values = new HashMap<String, String>(); |
| values.put("field1", value.toString()); |
| |
| for (String charset : charsets) { |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| bos.write(("\r\n--" + boundary + "\r\n").getBytes("ISO-8859-1")); |
| bos.write(("Content-Disposition: form-data; name=\"field1\"\r\n").getBytes("ISO-8859-1")); |
| bos.write(("Content-Type: text/plain; charset=" + charset + "\r\n\r\n").getBytes("ISO-8859-1")); |
| bos.write(value.toString().getBytes(charset)); |
| bos.write(("\r\n--" + boundary + "--\r\n").getBytes("ISO-8859-1")); |
| bos.write(("This is an epilogue.").getBytes("ISO-8859-1")); |
| |
| MultipartParser parser = prepareParser(boundary, bos.toByteArray()); |
| |
| assertMultipartBasics(parser, 1, values, false, null, null, null); |
| } |
| } |
| |
| @Test |
| public void testCharsetsAsExtraField() throws Exception { |
| String[] charsets = new String[] { "utf-8", "iso-8859-1", "utf-16" }; |
| |
| String boundary = "ldchqeriuvoqeirbvxipu eckqnqklwjcnqwklcqwncqewlciqecqwecevoipooei cqwcoewcq"; |
| StringBuilder value = new StringBuilder(); |
| |
| for (int i = 1; i < 255; i++) { |
| value.append((char) i); |
| } |
| |
| Map<String, String> values = new HashMap<String, String>(); |
| values.put("field1", value.toString()); |
| |
| for (String charset : charsets) { |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| bos.write(("\r\n--" + boundary + "\r\n").getBytes("ISO-8859-1")); |
| bos.write(("Content-Disposition: form-data; name=\"field1\"\r\n\r\n").getBytes("ISO-8859-1")); |
| bos.write(value.toString().getBytes(charset)); |
| bos.write(("\r\n--" + boundary + "\r\n").getBytes("ISO-8859-1")); |
| bos.write(("Content-Disposition: form-data; name=\"_charset_\"\r\n\r\n").getBytes("ISO-8859-1")); |
| bos.write(charset.getBytes("ISO-8859-1")); |
| bos.write(("\r\n--" + boundary + "--\r\n").getBytes("ISO-8859-1")); |
| bos.write(("This is an epilogue.").getBytes("ISO-8859-1")); |
| |
| MultipartParser parser = prepareParser(boundary, bos.toByteArray()); |
| |
| assertMultipartBasics(parser, 1, values, false, null, null, null); |
| } |
| } |
| |
| @Test |
| public void testCharsetsMixed() throws Exception { |
| String[] charsets = new String[] { "utf-8", "iso-8859-1", "utf-16" }; |
| |
| String boundary = "ldchqeriuvoqeirbvxipu eckqnqklwjcnqwklcqwncqewlciqecqwecevoipooei cqwcoewcq"; |
| StringBuilder value = new StringBuilder(); |
| |
| for (int i = 1; i < 255; i++) { |
| value.append((char) i); |
| } |
| |
| Map<String, String> values = new HashMap<String, String>(); |
| values.put("field1", value.toString()); |
| values.put("field2", value.toString()); |
| values.put("field3", value.toString()); |
| |
| for (String charset : charsets) { |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| bos.write(("\r\n--" + boundary + "\r\n").getBytes("ISO-8859-1")); |
| bos.write(("Content-Disposition: form-data; name=\"field1\"\r\n").getBytes("ISO-8859-1")); |
| bos.write(("Content-Type: text/plain; charset=\"utf-8\"\r\n\r\n").getBytes("ISO-8859-1")); |
| bos.write(value.toString().getBytes("utf-8")); |
| bos.write(("\r\n--" + boundary + "\r\n").getBytes("ISO-8859-1")); |
| bos.write(("Content-Disposition: form-data; name=\"field2\"\r\n\r\n").getBytes("ISO-8859-1")); |
| bos.write(value.toString().getBytes(charset)); |
| bos.write(("\r\n--" + boundary + "\r\n").getBytes("ISO-8859-1")); |
| bos.write(("Content-Disposition: form-data; name=\"_charset_\"\r\n\r\n").getBytes("ISO-8859-1")); |
| bos.write(charset.getBytes("ISO-8859-1")); |
| bos.write(("\r\n--" + boundary + "\r\n").getBytes("ISO-8859-1")); |
| bos.write(("Content-Disposition: form-data; name=\"field3\"\r\n\r\n").getBytes("ISO-8859-1")); |
| bos.write(value.toString().getBytes(charset)); |
| bos.write(("\r\n--" + boundary + "--\r\n").getBytes("ISO-8859-1")); |
| |
| MultipartParser parser = prepareParser(boundary, bos.toByteArray()); |
| |
| assertMultipartBasics(parser, 3, values, false, null, null, null); |
| } |
| } |
| |
| @Test(expected = CmisInvalidArgumentException.class) |
| public void testNoBoundary() throws Exception { |
| String boundary = ""; |
| byte[] formdata = ("\r\n--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"field1\"\r\n" |
| + "\r\n" + "value1\r\n" + "--" + boundary + "--").getBytes(); |
| |
| prepareParser(boundary, formdata); |
| } |
| |
| @Test(expected = CmisInvalidArgumentException.class) |
| public void testInvalidCharset() throws Exception { |
| String boundary = "15983409582340582340"; |
| byte[] formdata = ("\r\n--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"field1\"\r\n" |
| + "Content-Type: text/plain; charset=xyz\r\n" + "\r\n" + "value1\r\n" + "--" + boundary + "--") |
| .getBytes(); |
| |
| MultipartParser parser = prepareParser(boundary, formdata); |
| |
| assertMultipartBasics(parser, 1, null, false, null, null, null); |
| } |
| |
| @Test(expected = CmisInvalidArgumentException.class) |
| public void testTwoContentParts() throws Exception { |
| String boundary = "-?-"; |
| byte[] content = "abc������".getBytes(); |
| byte[] formdata = ("\r\n--" + boundary + "\r\n" |
| + "Content-Disposition: form-data; name=\"content1\"; filename=\"file1\"\r\n" |
| + "Content-Type: application/something\r\n" + "Content-Transfer-Encoding: binary\r\n" + "\r\n" |
| + new String(content) + "\r\n--" + boundary + "\r\n" |
| + "Content-Disposition: form-data; name=\"content2\"; filename=\"file2\"\r\n" |
| + "Content-Type: application/something\r\n" + "Content-Transfer-Encoding: binary\r\n" + "\r\n" |
| + new String(content) + "\r\n" + "--" + boundary + "--").getBytes(); |
| |
| MultipartParser parser = prepareParser(boundary, formdata); |
| |
| assertMultipartBasics(parser, 2, null, true, "file1", "application/something", content); |
| } |
| |
| // ---- helpers ---- |
| |
| private MultipartParser prepareParser(String boundary, byte[] content) throws Exception { |
| HttpServletRequest request = HttpRequestMockHelper.createMultipartRequest(boundary, content); |
| |
| TempStoreOutputStreamFactory streamFactory = TempStoreOutputStreamFactory.newInstance(null, THRESHOLD, |
| MAX_SIZE, false); |
| return new MultipartParser(request, streamFactory); |
| } |
| |
| private byte[] readBytesFromStream(InputStream is) throws IOException { |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| |
| byte[] buffer = new byte[4096]; |
| int b; |
| while ((b = is.read(buffer)) > -1) { |
| bos.write(buffer, 0, b); |
| } |
| |
| is.close(); |
| |
| return bos.toByteArray(); |
| } |
| |
| private void assertMultipartBasics(MultipartParser parser, int count, Map<String, String> values, |
| boolean hasContent, String filename, String contentType, byte[] content) throws Exception { |
| int counter = 0; |
| |
| parser.parse(); |
| |
| if (parser.hasContent()) { |
| counter++; |
| assertTrue(hasContent); |
| assertEquals(filename, parser.getFilename()); |
| assertEquals(contentType, parser.getContentType()); |
| assertEquals(content.length, parser.getSize().intValue()); |
| assertArrayEquals(content, readBytesFromStream(parser.getStream())); |
| } else { |
| assertFalse(hasContent); |
| } |
| |
| Map<String, String[]> fields = parser.getFields(); |
| for (Map.Entry<String, String[]> e : fields.entrySet()) { |
| assertNotNull(e.getValue()); |
| assertEquals(1, e.getValue().length); |
| |
| String fieldName = e.getKey(); |
| String fieldValue = e.getValue()[0]; |
| |
| assertEquals(fieldValue, values.get(fieldName)); |
| |
| counter++; |
| } |
| |
| assertEquals(count, counter); |
| assertEquals(counter - (hasContent ? 1 : 0), fields.size()); |
| } |
| } |