Skip to content

Commit

Permalink
Bugfix: Properly handle special characters in tagger ID
Browse files Browse the repository at this point in the history
  • Loading branch information
cygri committed May 15, 2015
1 parent f3dd178 commit 3b47a56
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/main/java/org/topbraid/mauiserver/MauiServer.java
Expand Up @@ -43,7 +43,7 @@ public Resource getResource(String requestURI, ServletContext context) {
return new HomeResource(context, taggers);
}
String taggerId = path[0];
Tagger tagger = taggers.getTagger(taggerId);
Tagger tagger = taggers.getTagger(TaggerResource.decodeTaggerIdFromURL(taggerId));
if (tagger == null) return null;
if (path.length == 1) {
return new TaggerResource(context, taggers, tagger);
Expand Down
24 changes: 22 additions & 2 deletions src/main/java/org/topbraid/mauiserver/TaggerResource.java
@@ -1,5 +1,9 @@
package org.topbraid.mauiserver;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

import javax.servlet.ServletContext;

import org.topbraid.mauiserver.framework.Request;
Expand All @@ -8,8 +12,8 @@
import org.topbraid.mauiserver.framework.Resource.Gettable;
import org.topbraid.mauiserver.framework.Response;
import org.topbraid.mauiserver.framework.Response.JSONResponse;
import org.topbraid.mauiserver.tagger.TaggerCollection;
import org.topbraid.mauiserver.tagger.Tagger;
import org.topbraid.mauiserver.tagger.TaggerCollection;

import com.entopix.maui.vocab.VocabularyStore;
import com.fasterxml.jackson.databind.node.ObjectNode;
Expand Down Expand Up @@ -65,6 +69,22 @@ public Response doDelete(Request request) {
}

public static String getRelativeTaggerURL(Tagger tagger) {
return "/" + tagger.getId();
return "/" + encodeTaggerIdForURL(tagger.getId());
}

public static String encodeTaggerIdForURL(String id) {
try {
return URLEncoder.encode(id, "utf-8");
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException("Can't happen", ex);
}
}

public static String decodeTaggerIdFromURL(String id) {
try {
return URLDecoder.decode(id, "utf-8");
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException("Can't happen", ex);
}
}
}
34 changes: 31 additions & 3 deletions src/main/java/org/topbraid/mauiserver/framework/RootServlet.java
Expand Up @@ -8,6 +8,8 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.topbraid.mauiserver.MauiServer;
import org.topbraid.mauiserver.framework.Resource.Deletable;
import org.topbraid.mauiserver.framework.Resource.Gettable;
Expand All @@ -16,7 +18,8 @@

@SuppressWarnings("serial")
public class RootServlet extends HttpServlet {

Logger log = LoggerFactory.getLogger(RootServlet.class);

/**
* Creates the server instance. This instance will be shared between
* servlets through the {@link ServletContext}. If this was a true
Expand Down Expand Up @@ -56,16 +59,41 @@ private void doRequest(HttpServletRequest req, HttpServletResponse resp)
Request request = createRequest(req, resp);
Response response;
try {
String requestURI = (req.getServletPath() == null || "".equals(req.getServletPath()))
? "/" : req.getServletPath();
String requestURI = getLocalRequestUriWithoutQuery(req);
log.debug(req.getMethod() + " " + requestURI);
Resource resource = getServer(req.getServletContext()).getResource(requestURI, req.getServletContext());
response = createResponse(request, resource);
} catch (Exception ex) {
log.error("Uncaught exception in servlet", ex);
response = request.serverError(ex);
}
response.send();
}

/**
* Returns a processed version of the request URI. The scheme, host
* name and port are omitted. The query string, such as
* <code>?param1=value&param2=value</code>, is omitted. If the webapp
* doesn't run as the servlet container's root application, then the
* path to the application is omitted. The resulting string always
* starts with a '/'. For example, if the webapp is deployed as
* <code>webapp-name</code>, then the URI
* <code>http://myserver:8080/webapp-name/foo/bar?x=123</code> would be
* turned into <code>/foo/bar</code>.
*/
private String getLocalRequestUriWithoutQuery(HttpServletRequest req) {
String result = req.getRequestURI();
if (req.getContextPath() != null) {
if (req.getRequestURI().startsWith(req.getContextPath())) {
return result.substring(req.getContextPath().length());
}
log.warn("Mismatch between context path '" +
req.getContextPath() + "' and request URI '" +
req.getRequestURI() + "'");
}
return result;
}

private Response createResponse(Request request, Resource resource) {
if (resource == null) {
return request.notFound();
Expand Down
47 changes: 44 additions & 3 deletions src/main/java/org/topbraid/mauiserver/tagger/TaggerStore.java
Expand Up @@ -6,6 +6,9 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
Expand Down Expand Up @@ -58,7 +61,12 @@ public Collection<String> listTaggers() {
List<String> results = new ArrayList<String>();
for (File f: path.listFiles()) {
if (!f.isDirectory()) continue;
results.add(f.getName());
String taggerId = decodeTaggerIdFromFilename(f.getName());
if (encodeTaggerIdAsFilename(taggerId).equals(f.getName())) {
results.add(taggerId);
} else {
log.warn("Skipping malformed tagger directory " + f.getName());
}
}
log.debug("Listed " + results.size() + " taggers: [" + StringUtils.join(results, ", ") + "]");
return results;
Expand Down Expand Up @@ -221,10 +229,43 @@ public void writeMauiModel(String taggerId, MauiFilter mauiModel) {
}

private File getTaggerDirectory(String id) {
return new File(path.getAbsolutePath() + "/" + id);
return new File(path.getAbsolutePath() + "/" + encodeTaggerIdAsFilename(id));
}

private File getTaggerFile(String id, String filename) {
return new File(path.getAbsolutePath() + "/" + id + "/" + filename);
return new File(path.getAbsolutePath() + "/" + encodeTaggerIdAsFilename(id) + "/" + filename);
}

/**
* Encodes special characters in a string to make it safe for use
* as a filename. This is approximate; it encodes many characters
* that are in fact safe. Characters are %-encoded.
*
* @param s An arbitrary string
* @return A version of the input that is safe for use as a filename
*/
private String encodeTaggerIdAsFilename(String s) {
try {
s = URLEncoder.encode(s, "utf-8");
if (s.startsWith(".")) {
// Catch cases like "." and ".."
s = "%2E" + s.substring(1);
}
if (s.startsWith("~")) {
// Catch cases like "~" and "~root"
s = "%7E" + s.substring(1);
}
return s;
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException("Can't happen", ex);
}
}

private String decodeTaggerIdFromFilename(String s) {
try {
return URLDecoder.decode(s, "utf-8");
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException("Can't happen", ex);
}
}
}

0 comments on commit 3b47a56

Please sign in to comment.