package net.gnehzr.tnoodle.server; import static net.gnehzr.tnoodle.utils.GwtSafeUtils.azzert; import static net.gnehzr.tnoodle.utils.GsonUtils.GSON; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.logging.Level; import java.util.logging.Logger; import java.util.Scanner; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.gnehzr.tnoodle.utils.Utils; import net.gnehzr.tnoodle.utils.GwtSafeUtils; import com.petebevin.markdown.MarkdownProcessor; @SuppressWarnings("serial") public abstract class SafeHttpServlet extends HttpServlet { private static final Logger l = Logger.getLogger(SafeHttpServlet.class.getName()); @Override protected final void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { LinkedHashMap<String, String> query = parseQuery(request.getQueryString()); String pathInfo = request.getPathInfo(); String[] path; if(pathInfo != null) { path = pathInfo.substring(1).split("/"); // skip leading / } else { path = new String[0]; } try { wrappedService(request, response, path, query); } catch(Throwable e) { sendError(request, response, e); } } public static String getCompletePath(HttpServletRequest request) { String path = request.getServletPath(); if(request.getPathInfo() != null) { path += request.getPathInfo(); } return path; } public static String getExtension(HttpServletRequest request) { String[] filename_ext = GwtSafeUtils.parseExtension(getCompletePath(request)); return filename_ext[1]; } protected abstract void wrappedService(HttpServletRequest request, HttpServletResponse response, String[] path, LinkedHashMap<String, String> query) throws Exception; public static LinkedHashMap<String, String> parseQuery(String query) { LinkedHashMap<String, String> queryMap = new LinkedHashMap<String, String>(); if(query == null) { return queryMap; } String[] pairs = query.split("&"); for(String pair : pairs) { String[] key_value = pair.split("="); if(key_value.length == 1) { queryMap.put(key_value[0], ""); // this allows for flags such as http://foo/blah?kill&burn } else { try { queryMap.put(key_value[0], URLDecoder.decode(key_value[1], "utf-8")); } catch (UnsupportedEncodingException e) { queryMap.put(key_value[0], key_value[1]); //worst case, just put the undecoded string } } } return queryMap; } protected static void sendJS(HttpServletRequest request, HttpServletResponse response, String js) { sendBytes(request, response, js.getBytes(), "application/javascript"); //TODO - charset? } protected static void sendJSON(HttpServletRequest request, HttpServletResponse response, String json) { String callback = parseQuery(request.getQueryString()).get("callback"); String ext = getExtension(request); // Here we enforce that JSON is only sent in response to URLs ending in .json. // This is important, because if clients were to expect to get JSON from urls not ending in // .json, our catch(Throwable e) {...} in handle() above will wrap up exceptions // as text, and we'll respond to a request that expects JSON with plaintext. azzert("json".equals(ext), "Attempted to respond with JSON to a url not ending in .json."); response.setHeader("Access-Control-Allow-Origin", "*"); //this allows x-domain ajax if(callback != null) { json = callback + "(" + json + ")"; } sendBytes(request, response, json.getBytes(), "application/json"); //TODO - charset? } protected static void sendError(HttpServletRequest request, HttpServletResponse response, Throwable error) { sendError(request, response, Utils.throwableToString(error)); } protected static void sendError(HttpServletRequest request, HttpServletResponse response, String error) { String extension = getExtension(request); if("json".equals(extension)) { HashMap<String, String> json = new HashMap<String, String>(); json.put("error", error); sendJSON(request, response, GSON.toJson(json)); } else { sendText(request, response, error); } } protected static void sendBytes(HttpServletRequest request, HttpServletResponse response, ByteArrayOutputStream bytes, String contentType) { try { response.setContentType(contentType); response.setContentLength(bytes.size()); bytes.writeTo(response.getOutputStream()); } catch (IOException e) { // This happens whenever the client closes the connection before we // got a chance to respond. No reason to freak out. l.log(Level.INFO, "", e); } } protected static void sendBytes(HttpServletRequest request, HttpServletResponse response, byte[] bytes, String contentType) { try { response.setContentType(contentType); response.setContentLength(bytes.length); response.getOutputStream().write(bytes); } catch (IOException e) { // This happens whenever the client closes the connection before we // got a chance to respond. No reason to freak out. l.log(Level.INFO, "", e); } } protected static void sendHtml(HttpServletRequest request, HttpServletResponse response, ByteArrayOutputStream bytes) { sendBytes(request, response, bytes, "text/html"); } protected static void sendHtml(HttpServletRequest request, HttpServletResponse response, byte[] bytes) { sendBytes(request, response, bytes, "text/html"); } protected static void sendText(HttpServletRequest request, HttpServletResponse response, String text) { sendBytes(request, response, text.getBytes(), "text/plain"); //TODO - encoding charset? } private static final MarkdownProcessor mp = new MarkdownProcessor(); private static String markdownToHTML(String dataString) { String titleCode = ""; // We assume that a title line is the first line, starts with one #, and possibly ends with one # if (dataString.startsWith("#")) { String title = new Scanner(dataString).nextLine(); title = title.substring(1); if (title.endsWith("#")) { title = title.substring(0, title.length()-1); } title = title.trim(); titleCode = "<title>" + title + "</title>\n"; } return "<html><head>\n" + titleCode + "<link href=\"/css/markdown.css\" rel=\"stylesheet\" type=\"text/css\" />\n" + "</head>\n<body>\n" + mp.markdown(dataString) + "</body>\n</html>\n"; } protected static void sendMarkdown(HttpServletRequest request, HttpServletResponse response, String markdown) { sendHtml(request, response, markdownToHTML(markdown).getBytes()); } }