Skip to content

Commit 63a3b9e

Browse files
AS7-6850 Fix servlet content type issue
1 parent 733d7eb commit 63a3b9e

File tree

4 files changed

+181
-12
lines changed

4 files changed

+181
-12
lines changed

servlet/src/main/java/io/undertow/servlet/spec/HttpServletResponseImpl.java

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,15 @@ public final class HttpServletResponseImpl implements HttpServletResponse {
6161
private Integer bufferSize;
6262
private Long contentLength;
6363
private boolean insideInclude = false;
64-
private boolean charsetSet = false;
65-
private String contentType;
66-
private String charset;
6764
private Locale locale;
6865
private boolean responseDone = false;
6966

70-
public HttpServletResponseImpl(final HttpServerExchange exchange, final ServletContextImpl servletContext) {
67+
68+
private boolean charsetSet = false; //if a content type has been set either implicitly or implicitly
69+
private String contentType;
70+
private String charset;
71+
72+
public HttpServletResponseImpl(final HttpServerExchange exchange, final ServletContextImpl servletContext) {
7173
this.exchange = exchange;
7274
this.servletContext = servletContext;
7375
}
@@ -264,7 +266,11 @@ public String getCharacterEncoding() {
264266
@Override
265267
public String getContentType() {
266268
if (contentType != null) {
267-
return contentType + ";charset=" + getCharacterEncoding();
269+
if (charsetSet) {
270+
return contentType + ";charset=" + getCharacterEncoding();
271+
} else {
272+
return contentType;
273+
}
268274
}
269275
return null;
270276
}
@@ -321,7 +327,7 @@ public void setContentLength(final int len) {
321327
return;
322328
}
323329
exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, "" + len);
324-
this.contentLength = (long)len;
330+
this.contentLength = (long) len;
325331
}
326332

327333
@Override
@@ -341,21 +347,40 @@ public void setContentType(final String type) {
341347
contentType = type;
342348
int split = type.indexOf(";");
343349
if (split != -1) {
344-
contentType = contentType.substring(0, split);
345350
int pos = type.indexOf("charset=");
346351
if (pos != -1) {
347352
int i = pos + "charset=".length();
348353
do {
349-
char c = type.charAt(i++);
354+
char c = type.charAt(i);
350355
if (c == ' ' || c == '\t' || c == ';') {
351356
break;
352357
}
358+
++i;
353359
} while (i < type.length());
354360
if (writer == null && !isCommitted()) {
355361
charsetSet = true;
356362
//we only change the charset if the writer has not been retrieved yet
357363
this.charset = type.substring(pos + "charset=".length(), i);
358364
}
365+
int charsetStart = pos;
366+
while (type.charAt(--charsetStart) != ';' && charsetStart >0) {}
367+
StringBuilder contentTypeBuilder = new StringBuilder();
368+
contentTypeBuilder.append(type.substring(0, charsetStart));
369+
if (i != type.length()) {
370+
contentTypeBuilder.append(type.substring(i));
371+
}
372+
contentType = contentTypeBuilder.toString();
373+
}
374+
//strip any trailing semicolon
375+
for (int i = contentType.length() - 1; i >= 0; --i) {
376+
char c = contentType.charAt(i);
377+
if (c == ' ' || c == '\t') {
378+
continue;
379+
}
380+
if(c == ';') {
381+
contentType = contentType.substring(0, i);
382+
}
383+
break;
359384
}
360385
}
361386
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, getContentType());
@@ -371,7 +396,7 @@ public void setBufferSize(final int size) {
371396

372397
@Override
373398
public int getBufferSize() {
374-
if(bufferSize == null){
399+
if (bufferSize == null) {
375400
return exchange.getConnection().getBufferSize();
376401
}
377402
return bufferSize;

servlet/src/test/java/io/undertow/servlet/test/listener/request/async/RequestListenerAsyncRequest.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,8 @@ public static void setup() throws ServletException {
7373
.setClassIntrospecter(TestClassIntrospector.INSTANCE)
7474
.setDeploymentName("servletContext.war")
7575
.setResourceLoader(TestResourceLoader.NOOP_RESOURCE_LOADER)
76-
.addServlets(m, a);
77-
78-
builder.addListener(new ListenerInfo(TestListener.class));
76+
.addServlets(m, a)
77+
.addListener(new ListenerInfo(TestListener.class));
7978

8079
DeploymentManager manager = container.addDeployment(builder);
8180
manager.deploy();
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* JBoss, Home of Professional Open Source.
3+
* Copyright 2012 Red Hat, Inc., and individual contributors
4+
* as indicated by the @author tags.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package io.undertow.servlet.test.response.contenttype;
20+
21+
import java.net.URLEncoder;
22+
23+
import javax.servlet.ServletException;
24+
25+
import io.undertow.server.handlers.PathHandler;
26+
import io.undertow.servlet.api.DeploymentInfo;
27+
import io.undertow.servlet.api.DeploymentManager;
28+
import io.undertow.servlet.api.ServletContainer;
29+
import io.undertow.servlet.api.ServletInfo;
30+
import io.undertow.servlet.test.SimpleServletTestCase;
31+
import io.undertow.servlet.test.util.TestClassIntrospector;
32+
import io.undertow.servlet.test.util.TestResourceLoader;
33+
import io.undertow.test.utils.DefaultServer;
34+
import io.undertow.test.utils.HttpClientUtils;
35+
import io.undertow.util.TestHttpClient;
36+
import org.apache.http.HttpResponse;
37+
import org.apache.http.client.methods.HttpGet;
38+
import org.junit.Assert;
39+
import org.junit.BeforeClass;
40+
import org.junit.Test;
41+
import org.junit.runner.RunWith;
42+
43+
/**
44+
* @author Stuart Douglas
45+
*/
46+
@RunWith(DefaultServer.class)
47+
public class ContentTypeCharsetTestCase {
48+
49+
@BeforeClass
50+
public static void setup() throws ServletException {
51+
52+
final PathHandler root = new PathHandler();
53+
final ServletContainer container = ServletContainer.Factory.newInstance();
54+
55+
ServletInfo m = new ServletInfo("charset", ContentTypeServlet.class)
56+
.addMapping("/*");
57+
58+
DeploymentInfo builder = new DeploymentInfo()
59+
.setClassLoader(SimpleServletTestCase.class.getClassLoader())
60+
.setContextPath("/servletContext")
61+
.setClassIntrospecter(TestClassIntrospector.INSTANCE)
62+
.setDeploymentName("servletContext.war")
63+
.setResourceLoader(TestResourceLoader.NOOP_RESOURCE_LOADER)
64+
.addServlets(m);
65+
66+
DeploymentManager manager = container.addDeployment(builder);
67+
manager.deploy();
68+
root.addPath(builder.getContextPath(), manager.start());
69+
70+
DefaultServer.setRootHandler(root);
71+
}
72+
73+
@Test
74+
public void testCharsetAndContentType() throws Exception {
75+
runtest("text/html", "UTF8", "text/html;charset=UTF8", "text/html;charset=UTF8\nUTF8");
76+
runtest("text/html", "", "text/html", "text/html\nISO-8859-1");
77+
runtest("text/html; charset=UTF8", "", "text/html;charset=UTF8", "text/html;charset=UTF8\nUTF8");
78+
runtest("text/html; charset=UTF8; boundary=someString;", "", "text/html; boundary=someString;charset=UTF8", "text/html; boundary=someString;charset=UTF8\nUTF8");
79+
runtest("text/html; charset=UTF8; boundary=someString; ", "", "text/html; boundary=someString;charset=UTF8", "text/html; boundary=someString;charset=UTF8\nUTF8");
80+
runtest("multipart/related; type=\"text/xml\"; boundary=\"uuid:ce7d652a-d035-42fa-962c-5b8315084e32\"; start=\"<root.message@cxf.apache.org>\"; start-info=\"text/xml\"", "", "multipart/related; type=\"text/xml\"; boundary=\"uuid:ce7d652a-d035-42fa-962c-5b8315084e32\"; start=\"<root.message@cxf.apache.org>\"; start-info=\"text/xml\"", "multipart/related; type=\"text/xml\"; boundary=\"uuid:ce7d652a-d035-42fa-962c-5b8315084e32\"; start=\"<root.message@cxf.apache.org>\"; start-info=\"text/xml\"\nISO-8859-1");
81+
82+
}
83+
84+
private void runtest(String contentType, String charset, String expectedContentType, String expectedBody) throws Exception {
85+
TestHttpClient client = new TestHttpClient();
86+
try {
87+
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/test?contentType=" + URLEncoder.encode(contentType) + "&charset=" + URLEncoder.encode(charset));
88+
HttpResponse result = client.execute(get);
89+
Assert.assertEquals(200, result.getStatusLine().getStatusCode());
90+
final String response = HttpClientUtils.readResponse(result);
91+
Assert.assertEquals(expectedContentType, result.getHeaders("Content-Type")[0].getValue());
92+
Assert.assertEquals(expectedBody, response);
93+
} finally {
94+
client.getConnectionManager().shutdown();
95+
}
96+
}
97+
98+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* JBoss, Home of Professional Open Source.
3+
* Copyright 2012 Red Hat, Inc., and individual contributors
4+
* as indicated by the @author tags.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package io.undertow.servlet.test.response.contenttype;
20+
21+
import java.io.IOException;
22+
23+
import javax.servlet.ServletException;
24+
import javax.servlet.http.HttpServlet;
25+
import javax.servlet.http.HttpServletRequest;
26+
import javax.servlet.http.HttpServletResponse;
27+
28+
/**
29+
* @author Stuart Douglas
30+
*/
31+
public class ContentTypeServlet extends HttpServlet {
32+
33+
@Override
34+
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
35+
36+
String contentType = req.getParameter("contentType");
37+
String charset = req.getParameter("charset");
38+
39+
if(contentType != null && !contentType.isEmpty()) {
40+
resp.setContentType(contentType);
41+
}
42+
if(charset != null && !charset.isEmpty()) {
43+
resp.setCharacterEncoding(charset);
44+
}
45+
resp.getWriter().print(resp.getContentType() + "\n" + resp.getCharacterEncoding());
46+
}
47+
}

0 commit comments

Comments
 (0)