/*
* Copyright 2002-2016 the original author or authors.
*
* 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 org.springframework.http.converter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.RequestContext;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.MockHttpInputMessage;
import org.springframework.http.MockHttpOutputMessage;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.never;
import static org.mockito.BDDMockito.verify;
/**
* @author Arjen Poutsma
* @author Rossen Stoyanchev
*/
public class FormHttpMessageConverterTests {
private final FormHttpMessageConverter converter = new AllEncompassingFormHttpMessageConverter();
@Test
public void canRead() {
assertTrue(this.converter.canRead(MultiValueMap.class,
new MediaType("application", "x-www-form-urlencoded")));
assertFalse(this.converter.canRead(MultiValueMap.class,
new MediaType("multipart", "form-data")));
}
@Test
public void canWrite() {
assertTrue(this.converter.canWrite(MultiValueMap.class,
new MediaType("application", "x-www-form-urlencoded")));
assertTrue(this.converter.canWrite(MultiValueMap.class,
new MediaType("multipart", "form-data")));
assertTrue(this.converter.canWrite(MultiValueMap.class,
new MediaType("multipart", "form-data", StandardCharsets.UTF_8)));
assertTrue(this.converter.canWrite(MultiValueMap.class, MediaType.ALL));
}
@Test
public void readForm() throws Exception {
String body = "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3";
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.ISO_8859_1));
inputMessage.getHeaders().setContentType(new MediaType("application", "x-www-form-urlencoded", StandardCharsets.ISO_8859_1));
MultiValueMap<String, String> result = this.converter.read(null, inputMessage);
assertEquals("Invalid result", 3, result.size());
assertEquals("Invalid result", "value 1", result.getFirst("name 1"));
List<String> values = result.get("name 2");
assertEquals("Invalid result", 2, values.size());
assertEquals("Invalid result", "value 2+1", values.get(0));
assertEquals("Invalid result", "value 2+2", values.get(1));
assertNull("Invalid result", result.getFirst("name 3"));
}
@Test
public void writeForm() throws IOException {
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.set("name 1", "value 1");
body.add("name 2", "value 2+1");
body.add("name 2", "value 2+2");
body.add("name 3", null);
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
this.converter.write(body, MediaType.APPLICATION_FORM_URLENCODED, outputMessage);
assertEquals("Invalid result", "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3",
outputMessage.getBodyAsString(StandardCharsets.UTF_8));
assertEquals("Invalid content-type", new MediaType("application", "x-www-form-urlencoded"),
outputMessage.getHeaders().getContentType());
assertEquals("Invalid content-length", outputMessage.getBodyAsBytes().length,
outputMessage.getHeaders().getContentLength());
}
@Test
public void writeMultipart() throws Exception {
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("name 1", "value 1");
parts.add("name 2", "value 2+1");
parts.add("name 2", "value 2+2");
parts.add("name 3", null);
Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg");
parts.add("logo", logo);
// SPR-12108
Resource utf8 = new ClassPathResource("/org/springframework/http/converter/logo.jpg") {
@Override
public String getFilename() {
return "Hall\u00F6le.jpg";
}
};
parts.add("utf8", utf8);
Source xml = new StreamSource(new StringReader("<root><child/></root>"));
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.setContentType(MediaType.TEXT_XML);
HttpEntity<Source> entity = new HttpEntity<>(xml, entityHeaders);
parts.add("xml", entity);
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
this.converter.write(parts, new MediaType("multipart", "form-data", StandardCharsets.UTF_8), outputMessage);
final MediaType contentType = outputMessage.getHeaders().getContentType();
assertNotNull("No boundary found", contentType.getParameter("boundary"));
// see if Commons FileUpload can read what we wrote
FileItemFactory fileItemFactory = new DiskFileItemFactory();
FileUpload fileUpload = new FileUpload(fileItemFactory);
RequestContext requestContext = new MockHttpOutputMessageRequestContext(outputMessage);
List<FileItem> items = fileUpload.parseRequest(requestContext);
assertEquals(6, items.size());
FileItem item = items.get(0);
assertTrue(item.isFormField());
assertEquals("name 1", item.getFieldName());
assertEquals("value 1", item.getString());
item = items.get(1);
assertTrue(item.isFormField());
assertEquals("name 2", item.getFieldName());
assertEquals("value 2+1", item.getString());
item = items.get(2);
assertTrue(item.isFormField());
assertEquals("name 2", item.getFieldName());
assertEquals("value 2+2", item.getString());
item = items.get(3);
assertFalse(item.isFormField());
assertEquals("logo", item.getFieldName());
assertEquals("logo.jpg", item.getName());
assertEquals("image/jpeg", item.getContentType());
assertEquals(logo.getFile().length(), item.getSize());
item = items.get(4);
assertFalse(item.isFormField());
assertEquals("utf8", item.getFieldName());
assertEquals("Hall\u00F6le.jpg", item.getName());
assertEquals("image/jpeg", item.getContentType());
assertEquals(logo.getFile().length(), item.getSize());
item = items.get(5);
assertEquals("xml", item.getFieldName());
assertEquals("text/xml", item.getContentType());
verify(outputMessage.getBody(), never()).close();
}
// SPR-13309
@Test
public void writeMultipartOrder() throws Exception {
MyBean myBean = new MyBean();
myBean.setString("foo");
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
parts.add("part1", myBean);
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.setContentType(MediaType.TEXT_XML);
HttpEntity<MyBean> entity = new HttpEntity<>(myBean, entityHeaders);
parts.add("part2", entity);
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
this.converter.setMultipartCharset(StandardCharsets.UTF_8);
this.converter.write(parts, new MediaType("multipart", "form-data", StandardCharsets.UTF_8), outputMessage);
final MediaType contentType = outputMessage.getHeaders().getContentType();
assertNotNull("No boundary found", contentType.getParameter("boundary"));
// see if Commons FileUpload can read what we wrote
FileItemFactory fileItemFactory = new DiskFileItemFactory();
FileUpload fileUpload = new FileUpload(fileItemFactory);
RequestContext requestContext = new MockHttpOutputMessageRequestContext(outputMessage);
List<FileItem> items = fileUpload.parseRequest(requestContext);
assertEquals(2, items.size());
FileItem item = items.get(0);
assertTrue(item.isFormField());
assertEquals("part1", item.getFieldName());
assertEquals("{\"string\":\"foo\"}", item.getString());
item = items.get(1);
assertTrue(item.isFormField());
assertEquals("part2", item.getFieldName());
// With developer builds we get: <MyBean><string>foo</string></MyBean>
// But on CI server we get: <MyBean xmlns=""><string>foo</string></MyBean>
// So... we make a compromise:
assertThat(item.getString(),
allOf(startsWith("<MyBean"), endsWith("><string>foo</string></MyBean>")));
}
private static class MockHttpOutputMessageRequestContext implements RequestContext {
private final MockHttpOutputMessage outputMessage;
private MockHttpOutputMessageRequestContext(MockHttpOutputMessage outputMessage) {
this.outputMessage = outputMessage;
}
@Override
public String getCharacterEncoding() {
MediaType type = this.outputMessage.getHeaders().getContentType();
return (type != null && type.getCharset() != null ? type.getCharset().name() : null);
}
@Override
public String getContentType() {
MediaType type = this.outputMessage.getHeaders().getContentType();
return (type != null ? type.toString() : null);
}
@Override
@Deprecated
public int getContentLength() {
return this.outputMessage.getBodyAsBytes().length;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.outputMessage.getBodyAsBytes());
}
}
public static class MyBean {
private String string;
public String getString() {
return this.string;
}
public void setString(String string) {
this.string = string;
}
}
}