Skip to content

Commit 7db9857

Browse files
aaimesikeoka
andauthored
[GEOS-11148] Update response headers for the Resources REST API (#7163)
Co-authored-by: Steve Ikeoka <steve.ikeoka@gdit.com>
1 parent ffce0fc commit 7db9857

File tree

2 files changed

+41
-30
lines changed

2 files changed

+41
-30
lines changed

Diff for: src/restconfig/src/main/java/org/geoserver/rest/resources/ResourceController.java

+23-23
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@
99
import com.thoughtworks.xstream.XStream;
1010
import com.thoughtworks.xstream.annotations.XStreamAlias;
1111
import freemarker.template.ObjectWrapper;
12-
import java.io.BufferedInputStream;
1312
import java.io.IOException;
14-
import java.io.InputStream;
1513
import java.io.UnsupportedEncodingException;
16-
import java.net.URLConnection;
1714
import java.net.URLDecoder;
1815
import java.text.DateFormat;
1916
import java.text.SimpleDateFormat;
@@ -23,6 +20,7 @@
2320
import java.util.Date;
2421
import java.util.List;
2522
import java.util.Locale;
23+
import java.util.Optional;
2624
import java.util.TimeZone;
2725
import java.util.logging.Level;
2826
import java.util.logging.Logger;
@@ -53,6 +51,7 @@
5351
import org.springframework.beans.factory.annotation.Qualifier;
5452
import org.springframework.context.annotation.Bean;
5553
import org.springframework.context.annotation.Configuration;
54+
import org.springframework.http.ContentDisposition;
5655
import org.springframework.http.HttpHeaders;
5756
import org.springframework.http.HttpStatus;
5857
import org.springframework.http.MediaType;
@@ -129,24 +128,18 @@ protected String getTemplateName(Object object) {
129128
* @return Content type requested
130129
*/
131130
protected static MediaType getMediaType(Resource resource, HttpServletRequest request) {
132-
if (resource.getType() == Resource.Type.DIRECTORY) {
133-
return getFormat(request);
134-
} else if (resource.getType() == Resource.Type.RESOURCE) {
135-
String mimeType = URLConnection.guessContentTypeFromName(resource.name());
136-
if (mimeType == null
137-
|| MediaType.APPLICATION_OCTET_STREAM.toString().equals(mimeType)) {
138-
// try guessing from data
139-
try (InputStream is = new BufferedInputStream(resource.in())) {
140-
mimeType = URLConnection.guessContentTypeFromStream(is);
141-
} catch (IOException e) {
142-
// do nothing, we'll just use application/octet-stream
143-
}
144-
}
145-
return mimeType == null
146-
? MediaType.APPLICATION_OCTET_STREAM
147-
: MediaType.valueOf(mimeType);
148-
} else {
149-
return null;
131+
switch (resource.getType()) {
132+
case DIRECTORY:
133+
return getFormat(request);
134+
case RESOURCE:
135+
// set the mime if known by the servlet container, otherwise default to
136+
// application/octet-stream to mitigate potential cross-site scripting
137+
return Optional.ofNullable(request.getServletContext())
138+
.map(sc -> sc.getMimeType(resource.name()))
139+
.map(MediaType::valueOf)
140+
.orElse(MediaType.APPLICATION_OCTET_STREAM);
141+
default:
142+
throw new ResourceNotFoundException("Undefined resource path.");
150143
}
151144
}
152145

@@ -265,21 +258,27 @@ public Object resourceGet(
265258
Resource resource = resource(request);
266259
Operation operation = operation(operationName);
267260
Object result;
268-
response.setContentType(getFormat(format).toString());
269261

270262
if (operation == Operation.METADATA) {
271263
result =
272264
wrapObject(
273265
new ResourceMetadataInfo(resource, request),
274266
ResourceMetadataInfo.class);
267+
response.setContentType(getFormat(format).toString());
275268
} else {
276269
if (resource.getType() == Resource.Type.UNDEFINED) {
277270
throw new ResourceNotFoundException("Undefined resource path.");
278271
} else {
279272
HttpHeaders responseHeaders = new HttpHeaders();
280273
MediaType mediaType = getMediaType(resource, request);
281274
responseHeaders.setContentType(mediaType);
282-
response.setContentType(mediaType.toString());
275+
if (resource.getType() == Resource.Type.RESOURCE) {
276+
// Use Content-Disposition: attachment to mitigate potential XSS issues
277+
responseHeaders.setContentDisposition(
278+
ContentDisposition.builder("attachment")
279+
.filename(resource.name())
280+
.build());
281+
}
283282

284283
if (request.getMethod().equals("HEAD")) {
285284
result = new ResponseEntity<>("", responseHeaders, HttpStatus.OK);
@@ -288,6 +287,7 @@ public Object resourceGet(
288287
wrapObject(
289288
new ResourceDirectoryInfo(resource, request),
290289
ResourceDirectoryInfo.class);
290+
response.setContentType(mediaType.toString());
291291
} else {
292292
result = new ResponseEntity<>(resource.in(), responseHeaders, HttpStatus.OK);
293293
}

Diff for: src/restconfig/src/test/java/org/geoserver/rest/resources/ResourceControllerTest.java

+18-7
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
package org.geoserver.rest.resources;
66

77
import static java.nio.charset.StandardCharsets.UTF_8;
8+
import static org.junit.Assert.assertEquals;
89
import static org.junit.Assert.assertSame;
910
import static org.junit.Assert.assertTrue;
1011

1112
import java.io.IOException;
1213
import java.io.InputStream;
1314
import java.io.OutputStreamWriter;
14-
import java.net.URLConnection;
1515
import java.nio.charset.Charset;
1616
import java.nio.charset.CharsetEncoder;
1717
import java.text.DateFormat;
@@ -239,6 +239,11 @@ public void testResourceMetadataHTML() throws Exception {
239239
public void testResourceHeaders() throws Exception {
240240
MockHttpServletResponse response =
241241
getAsServletResponse(RestBaseController.ROOT_PATH + "/resource/mydir2/fake.png");
242+
assertEquals(
243+
"http://localhost:8080/geoserver"
244+
+ RestBaseController.ROOT_PATH
245+
+ "/resource/mydir2/fake.png",
246+
response.getHeader("Location"));
242247
Assert.assertEquals(
243248
FORMAT_HEADER.format(getDataDirectory().get("mydir2/fake.png").lastmodified()),
244249
response.getHeader("Last-Modified"));
@@ -249,12 +254,19 @@ public void testResourceHeaders() throws Exception {
249254
response.getHeader("Resource-Parent"));
250255
Assert.assertEquals("resource", response.getHeader("Resource-Type"));
251256
assertContentType("image/png", response);
257+
assertEquals(
258+
"attachment; filename=\"fake.png\"", response.getHeader("Content-Disposition"));
252259
}
253260

254261
@Test
255262
public void testResourceHead() throws Exception {
256263
MockHttpServletResponse response =
257264
headAsServletResponse(RestBaseController.ROOT_PATH + "/resource/mydir2/fake.png");
265+
assertEquals(
266+
"http://localhost:8080/geoserver"
267+
+ RestBaseController.ROOT_PATH
268+
+ "/resource/mydir2/fake.png",
269+
response.getHeader("Location"));
258270
Assert.assertEquals(
259271
FORMAT_HEADER.format(getDataDirectory().get("mydir2/fake.png").lastmodified()),
260272
response.getHeader("Last-Modified"));
@@ -265,6 +277,8 @@ public void testResourceHead() throws Exception {
265277
response.getHeader("Resource-Parent"));
266278
Assert.assertEquals("resource", response.getHeader("Resource-Type"));
267279
assertContentType("image/png", response);
280+
assertEquals(
281+
"attachment; filename=\"fake.png\"", response.getHeader("Content-Disposition"));
268282
}
269283

270284
@Test
@@ -409,15 +423,15 @@ public void testDirectoryJSONMultipleChildren() throws Exception {
409423
+ " 'link': {\n"
410424
+ " 'href': 'http://localhost:8080/geoserver/rest/resource/mydir2/imagewithoutextension',\n"
411425
+ " 'rel': 'alternate',\n"
412-
+ " 'type': 'image/png'\n"
426+
+ " 'type': 'application/octet-stream'\n"
413427
+ " }\n"
414428
+ " },\n"
415429
+ " {\n"
416430
+ " 'name': 'myres.json',\n"
417431
+ " 'link': {\n"
418432
+ " 'href': 'http://localhost:8080/geoserver/rest/resource/mydir2/myres.json',\n"
419433
+ " 'rel': 'alternate',\n"
420-
+ " 'type': 'application/octet-stream'\n"
434+
+ " 'type': 'application/json'\n"
421435
+ " }\n"
422436
+ " },\n"
423437
+ " {\n"
@@ -430,9 +444,6 @@ public void testDirectoryJSONMultipleChildren() throws Exception {
430444
+ " }\n"
431445
+ " ]}\n"
432446
+ "}}";
433-
// starting with JDK 17 (v3?) json is correctly recognized, the test output
434-
String jsonType = URLConnection.guessContentTypeFromName("test.json");
435-
if (jsonType != null) expected = expected.replace("application/octet-stream", jsonType);
436447
JSONAssert.assertEquals(expected, (JSONObject) json);
437448
}
438449

@@ -508,7 +519,7 @@ public void testDirectoryMimeTypes() throws Exception {
508519
Document doc = getAsDOM(RestBaseController.ROOT_PATH + "/resource/mydir2?format=xml");
509520
// print(doc);
510521
XMLAssert.assertXpathEvaluatesTo(
511-
"image/png",
522+
"application/octet-stream",
512523
"/ResourceDirectory/children/child[name='imagewithoutextension']/atom:link/@type",
513524
doc);
514525
XMLAssert.assertXpathEvaluatesTo(

0 commit comments

Comments
 (0)