Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 1.23.x][GWC-1171] Improve handling special characters in the GWC Demos Page #1197

Merged
merged 1 commit into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
}
}