Skip to content

Commit

Permalink
[GWC-1171] Improve handling special characters in the GWC Demos Page
Browse files Browse the repository at this point in the history
  • Loading branch information
sikeoka authored and aaime committed Dec 4, 2023
1 parent eb11c81 commit 765a7e7
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
*/
package org.geowebcache;

import static org.apache.commons.text.StringEscapeUtils.escapeHtml4;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -473,7 +475,7 @@ private void handleFrontPage(HttpServletRequest request, HttpServletResponse res
baseUrl = "";
} else {
String[] strs = request.getRequestURL().toString().split("/");
baseUrl = strs[strs.length - 1] + "/";
baseUrl = escapeHtml4(strs[strs.length - 1]) + "/";
}

StringBuilder str = new StringBuilder();
Expand Down Expand Up @@ -576,20 +578,20 @@ private void appendStorageLocations(StringBuilder str) {
LOG.log(Level.SEVERE, "Could not find local cache location", ex);
}
str.append("<tr><th scope=\"row\">Config file:</th><td><tt>")
.append(configLoc)
.append(escapeHtml4(configLoc))
.append("</tt></td></tr>");
str.append("<tr><th scope=\"row\">Local Storage:</th><td><tt>")
.append(localStorageLoc)
.append(escapeHtml4(localStorageLoc))
.append("</tt></td></tr>");
str.append("</tbody>");
if (!blobStoreLocations.isEmpty()) {
str.append("<tbody>");
str.append("<tr><th scope=\"rowgroup\" colspan=\"2\">Blob Stores</th></tr>");
for (Map.Entry<String, String> e : blobStoreLocations.entrySet()) {
str.append("<tr><th scope=\"row\">")
.append(e.getKey())
.append(escapeHtml4(e.getKey()))
.append(":</th><td><tt>")
.append(e.getValue())
.append(escapeHtml4(e.getValue()))
.append("</tt></td></tr>");
}
str.append("</tbody>");
Expand Down
63 changes: 40 additions & 23 deletions geowebcache/core/src/main/java/org/geowebcache/demo/Demo.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
*/
package org.geowebcache.demo;

import static org.apache.commons.text.StringEscapeUtils.escapeHtml4;
import static org.owasp.encoder.Encode.forJavaScript;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -25,10 +28,12 @@
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.text.StringEscapeUtils;
import org.geotools.util.logging.Logging;
import org.geowebcache.GeoWebCacheException;
import org.geowebcache.filter.parameters.FloatParameterFilter;
import org.geowebcache.filter.parameters.ParameterFilter;
Expand All @@ -45,10 +50,13 @@
import org.geowebcache.mime.MimeType;
import org.geowebcache.mime.XMLMime;
import org.geowebcache.util.ServletUtils;
import org.owasp.encoder.Encode;
import org.springframework.util.Assert;

public class Demo {

private static Logger LOGGER = Logging.getLogger(Demo.class.getName());

public static void makeMap(
TileLayerDispatcher tileLayerDispatcher,
GridSetBroker gridSetBroker,
Expand Down Expand Up @@ -97,7 +105,7 @@ public static void makeMap(
response.sendRedirect(
response.encodeRedirectURL(reqUri.substring(0, reqUri.length() - 1)));
} catch (IOException e) {
e.printStackTrace();
LOGGER.log(Level.WARNING, "Error sending redirect response", e);
}
return;
} else {
Expand Down Expand Up @@ -181,11 +189,12 @@ private static void tableRows(
if (!layer.isAdvertised()) {
continue;
}
String escapedLayerName = escapeHtml4(layer.getName());
buf.append("<tr><td style=\"min-width: 100px;\"><strong>")
.append(layer.getName())
.append(escapedLayerName)
.append("</strong><br />\n");
buf.append("<a href=\"rest/seed/")
.append(layer.getName())
.append(escapedLayerName)
.append("\">Seed this layer</a>\n");
buf.append("</td><td>").append(layer.isEnabled()).append("</td>");
buf.append("<td><table width=\"100%\">");
Expand All @@ -196,6 +205,7 @@ private static void tableRows(
if (gridSetName.length() > 20) {
gridSetName = gridSetName.substring(0, 20) + "...";
}
gridSetName = escapeHtml4(gridSetName);
buf.append("<tr><td style=\"width: 170px;\">").append(gridSetName);

buf.append("</td><td>OpenLayers: [");
Expand All @@ -205,8 +215,8 @@ private static void tableRows(
.map(
type ->
generateDemoUrl(
layer.getName(),
gridSubset.getName(),
escapedLayerName,
escapeHtml4(gridSubset.getName()),
type))
.collect(Collectors.joining(", ")));

Expand Down Expand Up @@ -239,12 +249,12 @@ private static void outputKMLSupport(StringBuffer buf, TileLayer layer) {
if (type == XMLMime.kmz) {
return String.format(
"<a href=\"%sservice/kml/%s.kml.kmz\">kmz</a>",
prefix, layer.getName());
prefix, escapeHtml4(layer.getName()));
} else {
return String.format(
"<a href=\"%sservice/kml/%s.%s.kml\">%s</a>",
prefix,
layer.getName(),
escapeHtml4(layer.getName()),
type.getFileExtension(),
type.getFileExtension());
}
Expand Down Expand Up @@ -287,9 +297,9 @@ private static String generateHTML(TileLayer layer, String gridSetStr, String fo

buf.append("<html xmlns=\"http://www.w3.org/1999/xhtml\"><head>\n");
buf.append("<meta http-equiv=\"imagetoolbar\" content=\"no\">\n" + "<title>")
.append(layerName);
buf.append(" ").append(gridSubset.getName());
buf.append(" ").append(formatStr);
.append(escapeHtml4(layerName));
buf.append(" ").append(escapeHtml4(gridSubset.getName()));
buf.append(" ").append(escapeHtml4(formatStr));
buf.append("</title>\n");
buf.append(
"<style type=\"text/css\">\n"
Expand Down Expand Up @@ -368,17 +378,17 @@ private static String generateHTML(TileLayer layer, String gridSetStr, String fo
+ "}\n"
+ "\n");
buf.append("var gridsetName = '")
.append(gridSubset.getGridSet().getName())
.append(forJavaScript(gridSubset.getGridSet().getName()))
.append("';\n" + "var gridNames = ")
.append(
Arrays.stream(gridSubset.getGridNames())
.map(StringEscapeUtils::escapeEcmaScript)
.map(Encode::forJavaScript)
.map(s -> String.format("'%s'", s))
.collect(Collectors.joining(", ", "[", "]")))
.append(";\n" + "var baseUrl = '../service/wmts';\n" + "var style = '';\n");
buf.append("var format = '").append(formatStr).append("';\n");
buf.append("var format = '").append(forJavaScript(formatStr)).append("';\n");
buf.append("var infoFormat = 'text/html';\n");
buf.append("var layerName = '").append(layerName).append("';\n");
buf.append("var layerName = '").append(forJavaScript(layerName)).append("';\n");

String unit = "";
double mpu = gridSet.getMetersPerUnit();
Expand Down Expand Up @@ -672,7 +682,10 @@ private static String makeModifiableParameters(TileLayer tl) {
String key = pf.getKey();
String defaultValue = pf.getDefaultValue();
List<String> legalValues = pf.getLegalValues();
doc.append("<tr><td>").append(key.toUpperCase()).append(": ").append("</td><td>");
doc.append("<tr><td>")
.append(escapeHtml4(key.toUpperCase()))
.append(": ")
.append("</td><td>");
String parameterId = key;
if (pf instanceof StringParameterFilter) {
Map<String, String> keysValues = makeParametersMap(defaultValue, legalValues);
Expand Down Expand Up @@ -721,7 +734,11 @@ private static Map<String, String> makeParametersMap(
private static void makePullDown(
StringBuilder doc, String id, Map<String, String> keysValues, String defaultKey) {
doc.append(
"<select name=\"" + id + "\" onchange=\"window.setParam('" + id + "', value)\">\n");
"<select name=\""
+ escapeHtml4(id)
+ "\" onchange=\"window.setParam('"
+ forJavaScript(id)
+ "', value)\">\n");

Iterator<Entry<String, String>> iter = keysValues.entrySet().iterator();

Expand All @@ -732,16 +749,16 @@ private static void makePullDown(
if ((key == null && defaultKey == null) || (key != null && key.equals(defaultKey))) {
doc.append(
"<option value=\""
+ entry.getValue()
+ escapeHtml4(entry.getValue())
+ "\" selected=\"selected\">"
+ entry.getKey()
+ escapeHtml4(entry.getKey())
+ "</option>\n");
} else {
doc.append(
"<option value=\""
+ entry.getValue()
+ escapeHtml4(entry.getValue())
+ "\">"
+ entry.getKey()
+ escapeHtml4(entry.getKey())
+ "</option>\n");
}
}
Expand All @@ -752,11 +769,11 @@ private static void makePullDown(
private static void makeTextInput(StringBuilder doc, String id, int size) {
doc.append(
"<input name=\""
+ id
+ escapeHtml4(id)
+ "\" type=\"text\" size=\""
+ size
+ "\" onblur=\"window.setParam('"
+ id
+ forJavaScript(id)
+ "', value)\" />\n");
}

Expand Down
125 changes: 121 additions & 4 deletions geowebcache/core/src/test/java/org/geowebcache/DemoTest.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
package org.geowebcache;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
import org.geowebcache.demo.Demo;
import org.geowebcache.filter.parameters.RegexParameterFilter;
import org.geowebcache.filter.parameters.StringParameterFilter;
import org.geowebcache.grid.BoundingBox;
import org.geowebcache.grid.GridSet;
import org.geowebcache.grid.GridSetBroker;
import org.geowebcache.grid.GridSubset;
import org.geowebcache.grid.SRS;
import org.geowebcache.layer.TileLayer;
import org.geowebcache.layer.TileLayerDispatcher;
import org.geowebcache.layer.wms.WMSLayer;
import org.geowebcache.mime.MimeType;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
Expand Down Expand Up @@ -71,7 +83,112 @@ public void testAdvertised() throws GeoWebCacheException, IOException {
String result = response.getContentAsString();

// Ensure the Advertised Layer is present and the Unadvertised Layer is not
assertTrue(result.contains("testAdv"));
assertFalse(result.contains("testNotAdv"));
assertThat(result, containsString("testAdv"));
assertThat(result, not(containsString("testNotAdv")));
}

@Test
public void testEscapingWithoutLayer() throws Exception {
String unescapedLayer = "layer\"><";
String escapedLayer = "layer&quot;&gt;&lt;";
String unescapedSubset = "ESPG:1234\"><";
String escapedSubset = "ESPG:1234&quot;&gt;&lt;";
String epsg4326 = "ESPG:4326";

GridSubset subSet1 = mock(GridSubset.class);
when(subSet1.getName()).thenReturn(unescapedSubset);
GridSubset subSet2 = mock(GridSubset.class);
when(subSet2.getName()).thenReturn(epsg4326);
Set<String> gridSubsets = new LinkedHashSet<>();
gridSubsets.add(unescapedSubset);
gridSubsets.add(epsg4326);

GridSet gridSet = mock(GridSet.class);
when(gridSet.getName()).thenReturn(epsg4326);

GridSetBroker gsb = mock(GridSetBroker.class);
when(gsb.getWorldEpsg4326()).thenReturn(gridSet);

TileLayer layer = mock(TileLayer.class);
when(layer.getName()).thenReturn(unescapedLayer);
when(layer.isAdvertised()).thenReturn(true);
when(layer.getGridSubsets()).thenReturn(gridSubsets);
when(layer.getGridSubset(epsg4326)).thenReturn(subSet2);
when(layer.getGridSubset(unescapedSubset)).thenReturn(subSet1);
when(layer.getMimeTypes())
.thenReturn(Collections.singletonList(MimeType.createFromFormat("image/png")));

TileLayerDispatcher tld = mock(TileLayerDispatcher.class);
when(tld.getLayerNames()).thenReturn(Collections.singleton(unescapedLayer));
when(tld.getTileLayer(unescapedLayer)).thenReturn(layer);

MockHttpServletResponse response = new MockHttpServletResponse();
Demo.makeMap(tld, gsb, null, new MockHttpServletRequest(), response);
String result = response.getContentAsString();

assertThat(result, not(containsString(unescapedLayer)));
assertThat(result, containsString(escapedLayer));
assertThat(result, not(containsString(unescapedSubset)));
assertThat(result, containsString(escapedSubset));
}

@Test
public void testEscapingWithLayer() throws Exception {
String unescapedLayer = "layer'\"><";
String escapedLayer = "layer'&quot;&gt;&lt;";
String escapedLayerJS = "layer\\x27\\x22><";
String unescapedSubset = "ESPG:1234'\"><";
String escapedSubset = "ESPG:1234'&quot;&gt;&lt;";
String escapedSubsetJS = "ESPG:1234\\x27\\x22><";

GridSet gridSet = mock(GridSet.class);
when(gridSet.getName()).thenReturn(unescapedSubset);

GridSubset subSet = mock(GridSubset.class);
when(subSet.getName()).thenReturn(unescapedSubset);
when(subSet.getGridSet()).thenReturn(gridSet);
when(subSet.getGridNames()).thenReturn(new String[] {unescapedSubset});
when(subSet.getSRS()).thenReturn(SRS.getEPSG4326());
when(subSet.getGridSetBounds()).thenReturn(BoundingBox.WORLD4326);
when(subSet.getOriginalExtent()).thenReturn(BoundingBox.WORLD4326);

TileLayer layer = mock(TileLayer.class);
when(layer.getName()).thenReturn(unescapedLayer);
when(layer.isAdvertised()).thenReturn(true);
when(layer.getGridSubsets()).thenReturn(Collections.singleton(unescapedSubset));
when(layer.getGridSubset(unescapedSubset)).thenReturn(subSet);
when(layer.getDefaultMimeType()).thenReturn(MimeType.createFromFormat("image/png"));

String unescapedString = "string'\"><";
String escapedString = "string'&quot;&gt;&lt;";
String escapedStringJS = "string\\x27\\x22><";
String unescapedRegex = "regex'\"><";
String escapedRegex = "regex'&quot;&gt;&lt;";
String escapedRegexJS = "regex\\x27\\x22><";
StringParameterFilter stringFilter = new StringParameterFilter();
stringFilter.setKey(unescapedString);
RegexParameterFilter regexFilter = new RegexParameterFilter();
regexFilter.setKey(unescapedRegex);
when(layer.getParameterFilters()).thenReturn(Arrays.asList(stringFilter, regexFilter));

TileLayerDispatcher tld = mock(TileLayerDispatcher.class);
when(tld.getTileLayer(unescapedLayer)).thenReturn(layer);

MockHttpServletResponse response = new MockHttpServletResponse();
Demo.makeMap(tld, null, unescapedLayer, new MockHttpServletRequest(), response);
String result = response.getContentAsString();

assertThat(result, not(containsString(unescapedLayer)));
assertThat(result, containsString(escapedLayer));
assertThat(result, containsString(escapedLayerJS));
assertThat(result, not(containsString(unescapedSubset)));
assertThat(result, containsString(escapedSubset));
assertThat(result, containsString(escapedSubsetJS));
assertThat(result, not(containsString(unescapedString)));
assertThat(result, containsString(escapedString));
assertThat(result, containsString(escapedStringJS));
assertThat(result, not(containsString(unescapedRegex)));
assertThat(result, containsString(escapedRegex));
assertThat(result, containsString(escapedRegexJS));
}
}

0 comments on commit 765a7e7

Please sign in to comment.