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:
parent
7d5d159b55
commit
5588d971fc
1
pom.xml
1
pom.xml
@ -203,6 +203,7 @@
|
||||
|
||||
<properties>
|
||||
<slf4j.version>1.5.11</slf4j.version>
|
||||
<encoding>UTF-8</encoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@ -25,15 +25,6 @@
|
||||
|
||||
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.HttpClient;
|
||||
import org.apache.commons.httpclient.HttpConnectionManager;
|
||||
@ -52,6 +43,17 @@ import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
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.
|
||||
*/
|
||||
@ -60,7 +62,7 @@ public class HTTPUtils {
|
||||
|
||||
/**
|
||||
* Performs an HTTP GET on the given URL.
|
||||
*
|
||||
*
|
||||
* @param url The URL where to connect to.
|
||||
* @return The HTTP response as a String if the HTTP response code was 200
|
||||
* (OK).
|
||||
@ -73,7 +75,7 @@ public class HTTPUtils {
|
||||
/**
|
||||
* Performs an HTTP GET on the given URL. <BR>
|
||||
* Basic auth is used if both username and pw are not null.
|
||||
*
|
||||
*
|
||||
* @param url The URL where to connect to.
|
||||
* @param username Basic auth credential. No basic auth if null.
|
||||
* @param pw Basic auth credential. No basic auth if null.
|
||||
@ -87,8 +89,9 @@ public class HTTPUtils {
|
||||
HttpClient client = new HttpClient();
|
||||
HttpConnectionManager connectionManager = client.getHttpConnectionManager();
|
||||
try {
|
||||
setAuth(client, url, username, pw);
|
||||
httpMethod = new GetMethod(url);
|
||||
String encodedUrl = encodeUrl(url);
|
||||
setAuth(client, encodedUrl, username, pw);
|
||||
httpMethod = new GetMethod(encodedUrl);
|
||||
connectionManager.getParams().setConnectionTimeout(5000);
|
||||
int status = client.executeMethod(httpMethod);
|
||||
if (status == HttpStatus.SC_OK) {
|
||||
@ -117,10 +120,62 @@ public class HTTPUtils {
|
||||
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>
|
||||
* Basic auth is used if both username and pw are not null.
|
||||
*
|
||||
*
|
||||
* @param url The URL where to connect to.
|
||||
* @param file The File to be sent.
|
||||
* @param contentType The content-type to advert in the PUT.
|
||||
@ -138,7 +193,7 @@ public class HTTPUtils {
|
||||
/**
|
||||
* PUTs a String to the given URL. <BR>
|
||||
* Basic auth is used if both username and pw are not null.
|
||||
*
|
||||
*
|
||||
* @param url The URL where to connect to.
|
||||
* @param content The content to be sent as a String.
|
||||
* @param contentType The content-type to advert in the PUT.
|
||||
@ -161,7 +216,7 @@ public class HTTPUtils {
|
||||
/**
|
||||
* PUTs a String representing an XML document to the given URL. <BR>
|
||||
* Basic auth is used if both username and pw are not null.
|
||||
*
|
||||
*
|
||||
* @param url The URL where to connect to.
|
||||
* @param content The XML content to be sent as a String.
|
||||
* @param username Basic auth credential. No basic auth if null.
|
||||
@ -178,7 +233,7 @@ public class HTTPUtils {
|
||||
/**
|
||||
* Performs a PUT to the given URL. <BR>
|
||||
* Basic auth is used if both username and pw are not null.
|
||||
*
|
||||
*
|
||||
* @param url The URL where to connect to.
|
||||
* @param requestEntity The request to be sent.
|
||||
* @param username Basic auth credential. No basic auth if null.
|
||||
@ -195,7 +250,7 @@ public class HTTPUtils {
|
||||
/**
|
||||
* POSTs a File to the given URL. <BR>
|
||||
* Basic auth is used if both username and pw are not null.
|
||||
*
|
||||
*
|
||||
* @param url The URL where to connect to.
|
||||
* @param file The File to be sent.
|
||||
* @param contentType The content-type to advert in the POST.
|
||||
@ -213,7 +268,7 @@ public class HTTPUtils {
|
||||
/**
|
||||
* POSTs a String to the given URL. <BR>
|
||||
* Basic auth is used if both username and pw are not null.
|
||||
*
|
||||
*
|
||||
* @param url The URL where to connect to.
|
||||
* @param content The content to be sent as a String.
|
||||
* @param contentType The content-type to advert in the POST.
|
||||
@ -236,7 +291,7 @@ public class HTTPUtils {
|
||||
/**
|
||||
* POSTs a String representing an XML document to the given URL. <BR>
|
||||
* Basic auth is used if both username and pw are not null.
|
||||
*
|
||||
*
|
||||
* @param url The URL where to connect to.
|
||||
* @param content The XML content to be sent as a String.
|
||||
* @param username Basic auth credential. No basic auth if null.
|
||||
@ -253,7 +308,7 @@ public class HTTPUtils {
|
||||
/**
|
||||
* Performs a POST to the given URL. <BR>
|
||||
* Basic auth is used if both username and pw are not null.
|
||||
*
|
||||
*
|
||||
* @param url The URL where to connect to.
|
||||
* @param requestEntity The request to be sent.
|
||||
* @param username Basic auth credential. No basic auth if null.
|
||||
@ -279,15 +334,16 @@ public class HTTPUtils {
|
||||
* </UL>
|
||||
* are accepted as successful codes; in these cases the response string will
|
||||
* be returned.
|
||||
*
|
||||
*
|
||||
* @return the HTTP response or <TT>null</TT> on errors.
|
||||
*/
|
||||
private static String send(final EntityEnclosingMethod httpMethod, String url,
|
||||
RequestEntity requestEntity, String username, String pw) {
|
||||
HttpClient client = new HttpClient();
|
||||
HttpConnectionManager connectionManager = client.getHttpConnectionManager();
|
||||
String encodedUrl = encodeUrl(url);
|
||||
try {
|
||||
setAuth(client, url, username, pw);
|
||||
setAuth(client, encodedUrl, username, pw);
|
||||
connectionManager.getParams().setConnectionTimeout(5000);
|
||||
if (requestEntity != null)
|
||||
httpMethod.setRequestEntity(requestEntity);
|
||||
@ -304,15 +360,15 @@ public class HTTPUtils {
|
||||
return response;
|
||||
default:
|
||||
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()));
|
||||
return null;
|
||||
}
|
||||
} catch (ConnectException e) {
|
||||
LOGGER.info("Couldn't connect to [" + url + "]");
|
||||
LOGGER.info("Couldn't connect to [" + encodedUrl + "]");
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Error talking to " + url + " : " + e.getLocalizedMessage());
|
||||
LOGGER.error("Error talking to " + encodedUrl + " : " + e.getLocalizedMessage());
|
||||
return null;
|
||||
} finally {
|
||||
if (httpMethod != null)
|
||||
@ -326,9 +382,10 @@ public class HTTPUtils {
|
||||
DeleteMethod httpMethod = null;
|
||||
HttpClient client = new HttpClient();
|
||||
HttpConnectionManager connectionManager = client.getHttpConnectionManager();
|
||||
String encodedUrl = encodeUrl(url);
|
||||
try {
|
||||
setAuth(client, url, user, pw);
|
||||
httpMethod = new DeleteMethod(url);
|
||||
setAuth(client, encodedUrl, user, pw);
|
||||
httpMethod = new DeleteMethod(encodedUrl);
|
||||
connectionManager.getParams().setConnectionTimeout(5000);
|
||||
int status = client.executeMethod(httpMethod);
|
||||
String response = "";
|
||||
@ -336,23 +393,23 @@ public class HTTPUtils {
|
||||
InputStream is = httpMethod.getResponseBodyAsStream();
|
||||
response = IOUtils.toString(is);
|
||||
IOUtils.closeQuietly(is);
|
||||
if (response.trim().equals("")) {
|
||||
if (response.trim().equals("")) {
|
||||
if (LOGGER.isDebugEnabled())
|
||||
LOGGER
|
||||
.debug("ResponseBody is empty (this may be not an error since we just performed a DELETE call)");
|
||||
return true;
|
||||
}
|
||||
if (LOGGER.isDebugEnabled())
|
||||
LOGGER.debug("(" + status + ") " + httpMethod.getStatusText() + " -- " + url);
|
||||
LOGGER.debug("(" + status + ") " + httpMethod.getStatusText() + " -- " + encodedUrl);
|
||||
return true;
|
||||
} else {
|
||||
LOGGER.info("(" + status + ") " + httpMethod.getStatusText() + " -- " + url);
|
||||
LOGGER.info("(" + status + ") " + httpMethod.getStatusText() + " -- " + encodedUrl);
|
||||
LOGGER.info("Response: '" + response + "'");
|
||||
}
|
||||
} catch (ConnectException e) {
|
||||
LOGGER.info("Couldn't connect to [" + url + "]");
|
||||
LOGGER.info("Couldn't connect to [" + encodedUrl + "]");
|
||||
} catch (IOException e) {
|
||||
LOGGER.info("Error talking to [" + url + "]", e);
|
||||
LOGGER.info("Error talking to [" + encodedUrl + "]", e);
|
||||
} finally {
|
||||
if (httpMethod != null)
|
||||
httpMethod.releaseConnection();
|
||||
@ -375,12 +432,13 @@ public class HTTPUtils {
|
||||
HttpClient client = new HttpClient();
|
||||
HttpConnectionManager connectionManager = client.getHttpConnectionManager();
|
||||
try {
|
||||
setAuth(client, url, username, pw);
|
||||
httpMethod = new GetMethod(url);
|
||||
String encodedUrl = encodeUrl(url);
|
||||
setAuth(client, encodedUrl, username, pw);
|
||||
httpMethod = new GetMethod(encodedUrl);
|
||||
connectionManager.getParams().setConnectionTimeout(2000);
|
||||
int status = client.executeMethod(httpMethod);
|
||||
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;
|
||||
} else {
|
||||
return true;
|
||||
@ -399,7 +457,7 @@ public class HTTPUtils {
|
||||
|
||||
/**
|
||||
* Used to query for REST resources.
|
||||
*
|
||||
*
|
||||
* @param url The URL of the REST resource to query about.
|
||||
* @param username
|
||||
* @param pw
|
||||
@ -412,8 +470,9 @@ public class HTTPUtils {
|
||||
HttpClient client = new HttpClient();
|
||||
HttpConnectionManager connectionManager = client.getHttpConnectionManager();
|
||||
try {
|
||||
setAuth(client, url, username, pw);
|
||||
httpMethod = new GetMethod(url);
|
||||
String encodedUrl = encodeUrl(url);
|
||||
setAuth(client, encodedUrl, username, pw);
|
||||
httpMethod = new GetMethod(encodedUrl);
|
||||
connectionManager.getParams().setConnectionTimeout(2000);
|
||||
int status = client.executeMethod(httpMethod);
|
||||
switch (status) {
|
||||
@ -422,7 +481,7 @@ public class HTTPUtils {
|
||||
case HttpStatus.SC_NOT_FOUND:
|
||||
return false;
|
||||
default:
|
||||
throw new RuntimeException("Unhandled response status at '" + url + "': (" + status + ") "
|
||||
throw new RuntimeException("Unhandled response status at '" + encodedUrl + "': (" + status + ") "
|
||||
+ httpMethod.getStatusText());
|
||||
}
|
||||
} catch (ConnectException e) {
|
||||
@ -438,7 +497,9 @@ public class HTTPUtils {
|
||||
|
||||
private static void setAuth(HttpClient client, String url, String username, String pw)
|
||||
throws MalformedURLException {
|
||||
URL u = new URL(url);
|
||||
|
||||
String encodedUrl = encodeUrl(url);
|
||||
URL u = new URL(encodedUrl);
|
||||
if (username != null && pw != null) {
|
||||
Credentials defaultcreds = new UsernamePasswordCredentials(username, pw);
|
||||
client.getState().setCredentials(new AuthScope(u.getHost(), u.getPort()), defaultcreds);
|
||||
@ -449,14 +510,14 @@ public class HTTPUtils {
|
||||
// authentication
|
||||
} else {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Not setting credentials to access to " + url);
|
||||
LOGGER.debug("Not setting credentials to access to " + encodedUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param geoserverURL
|
||||
* @return recursively remove ending slashes
|
||||
* @return recursively remove ending slashes
|
||||
*/
|
||||
public static String decurtSlash(String geoserverURL) {
|
||||
if (geoserverURL!=null && geoserverURL.endsWith("/")) {
|
||||
@ -464,7 +525,7 @@ public class HTTPUtils {
|
||||
}
|
||||
return geoserverURL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param str a string array
|
||||
* @return create a StringBuilder appending all the passed arguments
|
||||
@ -473,7 +534,7 @@ public class HTTPUtils {
|
||||
if (str==null){
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
StringBuilder buf=new StringBuilder();
|
||||
for (String s: str){
|
||||
if (s!=null)
|
||||
@ -481,7 +542,7 @@ public class HTTPUtils {
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #append(String...)}
|
||||
* @param base base URL
|
||||
@ -492,7 +553,7 @@ public class HTTPUtils {
|
||||
if (str==null){
|
||||
return append(base.toString());
|
||||
}
|
||||
|
||||
|
||||
StringBuilder buf=new StringBuilder(base.toString());
|
||||
for (String s: str){
|
||||
if (s!=null)
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user