Encode path segments in urls to prevent illegal uris thanks to layer names with spaces.

I have a geoserver instance where users can publish layers to the geoserver.  Because there are so many users that can publish to the geoserver we have been unable to prevent them from publishing layers with spaces in the names.  When there are spaces (or other certain characters), the rest api
is broken because the urls have spaces in them.

Obviously the best solution is to fix Geoserver so that a layer with a space in the name (or in the workspace name) does not break the REST API.  However that is a substantial amount of work and this change makes this library more robust in the face or inconsiderate geoserver
administrators so I decided to make the change here.  When I have time I would like to patch Geoserver as well but that is another issue.
This commit is contained in:
Jesse Eichar 2014-04-24 08:33:15 +02:00
parent 7d5d159b55
commit 5588d971fc
3 changed files with 125 additions and 47 deletions

View File

@ -203,6 +203,7 @@
<properties> <properties>
<slf4j.version>1.5.11</slf4j.version> <slf4j.version>1.5.11</slf4j.version>
<encoding>UTF-8</encoding>
</properties> </properties>
<dependencies> <dependencies>

View File

@ -25,15 +25,6 @@
package it.geosolutions.geoserver.rest; package it.geosolutions.geoserver.rest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnectionManager; import org.apache.commons.httpclient.HttpConnectionManager;
@ -52,6 +43,17 @@ import org.apache.commons.io.IOUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
/** /**
* Low level HTTP utilities. * Low level HTTP utilities.
*/ */
@ -87,8 +89,9 @@ public class HTTPUtils {
HttpClient client = new HttpClient(); HttpClient client = new HttpClient();
HttpConnectionManager connectionManager = client.getHttpConnectionManager(); HttpConnectionManager connectionManager = client.getHttpConnectionManager();
try { try {
setAuth(client, url, username, pw); String encodedUrl = encodeUrl(url);
httpMethod = new GetMethod(url); setAuth(client, encodedUrl, username, pw);
httpMethod = new GetMethod(encodedUrl);
connectionManager.getParams().setConnectionTimeout(5000); connectionManager.getParams().setConnectionTimeout(5000);
int status = client.executeMethod(httpMethod); int status = client.executeMethod(httpMethod);
if (status == HttpStatus.SC_OK) { if (status == HttpStatus.SC_OK) {
@ -117,6 +120,58 @@ public class HTTPUtils {
return null; return null;
} }
static String encodeUrl(String url) {
// perform some simple encoding to the path names. This takes care of cases where
// layers or workspaces have illegal http characters.
// it cannot fix all
String protocol, authority, path, query, fragment = null;
String[] protocolPathParts = url.split("://", 2);
if (protocolPathParts.length == 1) {
// unexpected format so just try out url
return url;
}
protocol = protocolPathParts[0];
String[] pathQueryParts = protocolPathParts[1].split("\\?", 2);
path = pathQueryParts[0];
if (pathQueryParts.length == 1) {
query = null;
} else {
query = pathQueryParts[1];
}
if (query == null) {
String[] fragmentParts = path.split("#", 2);
if (fragmentParts.length > 1) {
path = fragmentParts[0];
fragment = fragmentParts[1];
}
} else {
String[] fragmentParts = query.split("#", 2);
if (fragmentParts.length > 1) {
query = fragmentParts[0];
fragment = fragmentParts[1];
}
}
int firstSlash = path.indexOf('/');
if (firstSlash > -1) {
authority = path.substring(0, firstSlash);
path = path.substring(firstSlash);
} else {
authority = path;
path = null;
}
try {
return new URI(protocol, authority, path, query, fragment).toString();
} catch (URISyntaxException e) {
// fallback to original string
return url;
}
}
/** /**
* PUTs a File to the given URL. <BR> * PUTs a File to the given URL. <BR>
* Basic auth is used if both username and pw are not null. * Basic auth is used if both username and pw are not null.
@ -286,8 +341,9 @@ public class HTTPUtils {
RequestEntity requestEntity, String username, String pw) { RequestEntity requestEntity, String username, String pw) {
HttpClient client = new HttpClient(); HttpClient client = new HttpClient();
HttpConnectionManager connectionManager = client.getHttpConnectionManager(); HttpConnectionManager connectionManager = client.getHttpConnectionManager();
String encodedUrl = encodeUrl(url);
try { try {
setAuth(client, url, username, pw); setAuth(client, encodedUrl, username, pw);
connectionManager.getParams().setConnectionTimeout(5000); connectionManager.getParams().setConnectionTimeout(5000);
if (requestEntity != null) if (requestEntity != null)
httpMethod.setRequestEntity(requestEntity); httpMethod.setRequestEntity(requestEntity);
@ -304,15 +360,15 @@ public class HTTPUtils {
return response; return response;
default: default:
LOGGER.warn("Bad response: code[" + status + "]" + " msg[" + httpMethod.getStatusText() + "]" LOGGER.warn("Bad response: code[" + status + "]" + " msg[" + httpMethod.getStatusText() + "]"
+ " url[" + url + "]" + " method[" + httpMethod.getClass().getSimpleName() + " url[" + encodedUrl + "]" + " method[" + httpMethod.getClass().getSimpleName()
+ "]: " + IOUtils.toString(httpMethod.getResponseBodyAsStream())); + "]: " + IOUtils.toString(httpMethod.getResponseBodyAsStream()));
return null; return null;
} }
} catch (ConnectException e) { } catch (ConnectException e) {
LOGGER.info("Couldn't connect to [" + url + "]"); LOGGER.info("Couldn't connect to [" + encodedUrl + "]");
return null; return null;
} catch (IOException e) { } catch (IOException e) {
LOGGER.error("Error talking to " + url + " : " + e.getLocalizedMessage()); LOGGER.error("Error talking to " + encodedUrl + " : " + e.getLocalizedMessage());
return null; return null;
} finally { } finally {
if (httpMethod != null) if (httpMethod != null)
@ -326,9 +382,10 @@ public class HTTPUtils {
DeleteMethod httpMethod = null; DeleteMethod httpMethod = null;
HttpClient client = new HttpClient(); HttpClient client = new HttpClient();
HttpConnectionManager connectionManager = client.getHttpConnectionManager(); HttpConnectionManager connectionManager = client.getHttpConnectionManager();
String encodedUrl = encodeUrl(url);
try { try {
setAuth(client, url, user, pw); setAuth(client, encodedUrl, user, pw);
httpMethod = new DeleteMethod(url); httpMethod = new DeleteMethod(encodedUrl);
connectionManager.getParams().setConnectionTimeout(5000); connectionManager.getParams().setConnectionTimeout(5000);
int status = client.executeMethod(httpMethod); int status = client.executeMethod(httpMethod);
String response = ""; String response = "";
@ -343,16 +400,16 @@ public class HTTPUtils {
return true; return true;
} }
if (LOGGER.isDebugEnabled()) if (LOGGER.isDebugEnabled())
LOGGER.debug("(" + status + ") " + httpMethod.getStatusText() + " -- " + url); LOGGER.debug("(" + status + ") " + httpMethod.getStatusText() + " -- " + encodedUrl);
return true; return true;
} else { } else {
LOGGER.info("(" + status + ") " + httpMethod.getStatusText() + " -- " + url); LOGGER.info("(" + status + ") " + httpMethod.getStatusText() + " -- " + encodedUrl);
LOGGER.info("Response: '" + response + "'"); LOGGER.info("Response: '" + response + "'");
} }
} catch (ConnectException e) { } catch (ConnectException e) {
LOGGER.info("Couldn't connect to [" + url + "]"); LOGGER.info("Couldn't connect to [" + encodedUrl + "]");
} catch (IOException e) { } catch (IOException e) {
LOGGER.info("Error talking to [" + url + "]", e); LOGGER.info("Error talking to [" + encodedUrl + "]", e);
} finally { } finally {
if (httpMethod != null) if (httpMethod != null)
httpMethod.releaseConnection(); httpMethod.releaseConnection();
@ -375,12 +432,13 @@ public class HTTPUtils {
HttpClient client = new HttpClient(); HttpClient client = new HttpClient();
HttpConnectionManager connectionManager = client.getHttpConnectionManager(); HttpConnectionManager connectionManager = client.getHttpConnectionManager();
try { try {
setAuth(client, url, username, pw); String encodedUrl = encodeUrl(url);
httpMethod = new GetMethod(url); setAuth(client, encodedUrl, username, pw);
httpMethod = new GetMethod(encodedUrl);
connectionManager.getParams().setConnectionTimeout(2000); connectionManager.getParams().setConnectionTimeout(2000);
int status = client.executeMethod(httpMethod); int status = client.executeMethod(httpMethod);
if (status != HttpStatus.SC_OK) { if (status != HttpStatus.SC_OK) {
LOGGER.warn("PING failed at '" + url + "': (" + status + ") " + httpMethod.getStatusText()); LOGGER.warn("PING failed at '" + encodedUrl + "': (" + status + ") " + httpMethod.getStatusText());
return false; return false;
} else { } else {
return true; return true;
@ -412,8 +470,9 @@ public class HTTPUtils {
HttpClient client = new HttpClient(); HttpClient client = new HttpClient();
HttpConnectionManager connectionManager = client.getHttpConnectionManager(); HttpConnectionManager connectionManager = client.getHttpConnectionManager();
try { try {
setAuth(client, url, username, pw); String encodedUrl = encodeUrl(url);
httpMethod = new GetMethod(url); setAuth(client, encodedUrl, username, pw);
httpMethod = new GetMethod(encodedUrl);
connectionManager.getParams().setConnectionTimeout(2000); connectionManager.getParams().setConnectionTimeout(2000);
int status = client.executeMethod(httpMethod); int status = client.executeMethod(httpMethod);
switch (status) { switch (status) {
@ -422,7 +481,7 @@ public class HTTPUtils {
case HttpStatus.SC_NOT_FOUND: case HttpStatus.SC_NOT_FOUND:
return false; return false;
default: default:
throw new RuntimeException("Unhandled response status at '" + url + "': (" + status + ") " throw new RuntimeException("Unhandled response status at '" + encodedUrl + "': (" + status + ") "
+ httpMethod.getStatusText()); + httpMethod.getStatusText());
} }
} catch (ConnectException e) { } catch (ConnectException e) {
@ -438,7 +497,9 @@ public class HTTPUtils {
private static void setAuth(HttpClient client, String url, String username, String pw) private static void setAuth(HttpClient client, String url, String username, String pw)
throws MalformedURLException { throws MalformedURLException {
URL u = new URL(url);
String encodedUrl = encodeUrl(url);
URL u = new URL(encodedUrl);
if (username != null && pw != null) { if (username != null && pw != null) {
Credentials defaultcreds = new UsernamePasswordCredentials(username, pw); Credentials defaultcreds = new UsernamePasswordCredentials(username, pw);
client.getState().setCredentials(new AuthScope(u.getHost(), u.getPort()), defaultcreds); client.getState().setCredentials(new AuthScope(u.getHost(), u.getPort()), defaultcreds);
@ -449,7 +510,7 @@ public class HTTPUtils {
// authentication // authentication
} else { } else {
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Not setting credentials to access to " + url); LOGGER.debug("Not setting credentials to access to " + encodedUrl);
} }
} }
} }

View File

@ -0,0 +1,16 @@
package it.geosolutions.geoserver.rest;
import junit.framework.TestCase;
public class HTTPUtilsTest extends TestCase {
public void testEncodeUrl() throws Exception {
assertEquals("http://with%20spaces", HTTPUtils.encodeUrl("http://with spaces"));
assertEquals("http://with%20spaces?p1=v1", HTTPUtils.encodeUrl("http://with spaces?p1=v1"));
assertEquals("http://without/spaces?p1=v1", HTTPUtils.encodeUrl("http://without/spaces?p1=v1"));
assertEquals("http://without/spaces", HTTPUtils.encodeUrl("http://without/spaces"));
assertEquals("http://without/spaces#fragment", HTTPUtils.encodeUrl("http://without/spaces#fragment"));
assertEquals("http://without/spaces?p1=v1#fragment", HTTPUtils.encodeUrl("http://without/spaces?p1=v1#fragment"));
assertEquals("http://with%20spaces#fragment", HTTPUtils.encodeUrl("http://with spaces#fragment"));
assertEquals("brokenurl?p1=v1", HTTPUtils.encodeUrl("brokenurl?p1=v1"));
}
}