From b8dd92a125149e63278622ff9fff4277dffd30e8 Mon Sep 17 00:00:00 2001 From: hns Date: Mon, 6 Sep 2004 13:48:30 +0000 Subject: [PATCH] Switch to Jakarta Commons Codec for URL en/decoding to provide encoding-sensitivity on JDK 1.3. Use application charset for URL encoding and decoding. UnsupportedEncodingException is propagated. --- src/helma/framework/core/Application.java | 18 +++--- src/helma/framework/core/RequestPath.java | 8 ++- src/helma/framework/core/Skin.java | 23 +++++--- src/helma/scripting/rhino/GlobalObject.java | 6 +- src/helma/scripting/rhino/HopObject.java | 9 ++- src/helma/scripting/rhino/JavaObject.java | 9 ++- src/helma/scripting/rhino/PathWrapper.java | 4 +- src/helma/scripting/rhino/RhinoCore.java | 4 +- src/helma/servlet/AbstractServletClient.java | 31 +++++----- src/helma/util/UrlEncoded.java | 59 +++++++++++++++----- 10 files changed, 108 insertions(+), 63 deletions(-) diff --git a/src/helma/framework/core/Application.java b/src/helma/framework/core/Application.java index 1d742829..9b4f3c39 100644 --- a/src/helma/framework/core/Application.java +++ b/src/helma/framework/core/Application.java @@ -602,7 +602,7 @@ public final class Application implements IPathElement, Runnable { try { res.close(charset); } catch (UnsupportedEncodingException uee) { - logEvent("Unsupported response encoding: " + uee.getMessage()); + logError("Unsupported response encoding", uee); } } else { res.waitForClose(); @@ -692,9 +692,9 @@ public final class Application implements IPathElement, Runnable { String rootFactory = classMapping.getProperty("root.factory.class"); Class c = typemgr.getClassLoader().loadClass(rootFactory); Method m = c.getMethod(classMapping.getProperty("root.factory.method"), - null); + (Class[]) null); - rootObject = m.invoke(c, null); + rootObject = m.invoke(c, (Object[]) null); } else { String rootClass = classMapping.getProperty("root"); Class c = typemgr.getClassLoader().loadClass(rootClass); @@ -1056,26 +1056,28 @@ public final class Application implements IPathElement, Runnable { /** * Return the href to the root of this application. */ - public String getRootHref() { + public String getRootHref() throws UnsupportedEncodingException { return getNodeHref(getDataRoot(), null); } /** * Return a path to be used in a URL pointing to the given element and action */ - public String getNodeHref(Object elem, String actionName) { + public String getNodeHref(Object elem, String actionName) + throws UnsupportedEncodingException { StringBuffer b = new StringBuffer(baseURI); composeHref(elem, b, 0); if (actionName != null) { - b.append(UrlEncoded.encode(actionName)); + b.append(UrlEncoded.smartEncode(actionName, charset)); } return b.toString(); } - private final void composeHref(Object elem, StringBuffer b, int pathCount) { + private final void composeHref(Object elem, StringBuffer b, int pathCount) + throws UnsupportedEncodingException { if ((elem == null) || (pathCount > 50)) { return; } @@ -1096,7 +1098,7 @@ public final class Application implements IPathElement, Runnable { // append ourselves String ename = getElementName(elem); if (ename != null) { - b.append(UrlEncoded.encode(ename)); + b.append(UrlEncoded.smartEncode(ename, charset)); b.append("/"); } } diff --git a/src/helma/framework/core/RequestPath.java b/src/helma/framework/core/RequestPath.java index 199f0111..3298d5f0 100644 --- a/src/helma/framework/core/RequestPath.java +++ b/src/helma/framework/core/RequestPath.java @@ -17,6 +17,8 @@ package helma.framework.core; import java.util.*; +import java.io.UnsupportedEncodingException; + import helma.util.UrlEncoded; /** @@ -106,7 +108,7 @@ public class RequestPath { /** * Returns the string representation of this path usable for links. */ - public String href(String action) { + public String href(String action) throws UnsupportedEncodingException { StringBuffer buffer = new StringBuffer(app.getBaseURI()); int start = 1; @@ -121,12 +123,12 @@ public class RequestPath { } for (int i=start; i 50) { throw new RuntimeException("Recursive skin invocation suspected"); @@ -369,7 +370,8 @@ public final class Skin { * Render the macro given a handler object */ public void render(RequestEvaluator reval, Object thisObject, Map paramObject, - Map handlerCache) throws RedirectException { + Map handlerCache) + throws RedirectException, UnsupportedEncodingException { if ((sandbox != null) && !sandbox.contains(fullName)) { //String h = (handler == null) ? "global" : handler; @@ -547,7 +549,8 @@ public final class Skin { } } - private void renderFromResponse(RequestEvaluator reval) { + private void renderFromResponse(RequestEvaluator reval) + throws UnsupportedEncodingException { Object value = null; if ("message".equals(name)) { @@ -563,7 +566,8 @@ public final class Skin { writeResponse(value, reval.res.getBuffer(), true); } - private void renderFromRequest(RequestEvaluator reval) { + private void renderFromRequest(RequestEvaluator reval) + throws UnsupportedEncodingException { if (reval.req == null) { return; } @@ -573,7 +577,8 @@ public final class Skin { writeResponse(value, reval.res.getBuffer(), true); } - private void renderFromSession(RequestEvaluator reval) { + private void renderFromSession(RequestEvaluator reval) + throws UnsupportedEncodingException { if (reval.session == null) { return; } @@ -583,7 +588,8 @@ public final class Skin { writeResponse(value, reval.res.getBuffer(), true); } - private void renderFromParam(RequestEvaluator reval, Map paramObject) { + private void renderFromParam(RequestEvaluator reval, Map paramObject) + throws UnsupportedEncodingException { if (paramObject == null) { reval.res.write("[HopMacro error: Skin requires a parameter object]"); } else { @@ -596,7 +602,8 @@ public final class Skin { /** * Utility method for writing text out to the response object. */ - void writeResponse(Object value, StringBuffer buffer, boolean useDefault) { + void writeResponse(Object value, StringBuffer buffer, boolean useDefault) + throws UnsupportedEncodingException { String text; if (value == null) { @@ -650,7 +657,7 @@ public final class Skin { break; case ENCODE_URL: - buffer.append(UrlEncoded.encode(text)); + buffer.append(UrlEncoded.smartEncode(text, app.charset)); break; diff --git a/src/helma/scripting/rhino/GlobalObject.java b/src/helma/scripting/rhino/GlobalObject.java index 42105990..d6db2cac 100644 --- a/src/helma/scripting/rhino/GlobalObject.java +++ b/src/helma/scripting/rhino/GlobalObject.java @@ -89,7 +89,8 @@ public class GlobalObject extends ImporterTopLevel { * * @return ... */ - public boolean renderSkin(Object skinobj, Object paramobj) { + public boolean renderSkin(Object skinobj, Object paramobj) + throws UnsupportedEncodingException { Context cx = Context.getCurrentContext(); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); @@ -122,7 +123,8 @@ public class GlobalObject extends ImporterTopLevel { * * @return ... */ - public String renderSkinAsString(Object skinobj, Object paramobj) { + public String renderSkinAsString(Object skinobj, Object paramobj) + throws UnsupportedEncodingException { Context cx = Context.getCurrentContext(); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); diff --git a/src/helma/scripting/rhino/HopObject.java b/src/helma/scripting/rhino/HopObject.java index c09e9191..8cf65489 100644 --- a/src/helma/scripting/rhino/HopObject.java +++ b/src/helma/scripting/rhino/HopObject.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.Map; +import java.io.UnsupportedEncodingException; /** * @@ -247,7 +248,8 @@ public class HopObject extends ScriptableObject implements Wrapper { * * @return ... */ - public boolean jsFunction_renderSkin(Object skinobj, Object paramobj) { + public boolean jsFunction_renderSkin(Object skinobj, Object paramobj) + throws UnsupportedEncodingException { Context cx = Context.getCurrentContext(); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); @@ -280,7 +282,8 @@ public class HopObject extends ScriptableObject implements Wrapper { * * @return ... */ - public String jsFunction_renderSkinAsString(Object skinobj, Object paramobj) { + public String jsFunction_renderSkinAsString(Object skinobj, Object paramobj) + throws UnsupportedEncodingException { Context cx = Context.getCurrentContext(); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); @@ -315,7 +318,7 @@ public class HopObject extends ScriptableObject implements Wrapper { * * @return ... */ - public Object jsFunction_href(Object action) { + public Object jsFunction_href(Object action) throws UnsupportedEncodingException { if (node == null) { return null; } diff --git a/src/helma/scripting/rhino/JavaObject.java b/src/helma/scripting/rhino/JavaObject.java index a8df2ef2..ce934da1 100644 --- a/src/helma/scripting/rhino/JavaObject.java +++ b/src/helma/scripting/rhino/JavaObject.java @@ -21,6 +21,7 @@ import org.mozilla.javascript.*; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; +import java.io.UnsupportedEncodingException; /** * @@ -68,7 +69,8 @@ public class JavaObject extends NativeJavaObject { * * @return ... */ - public boolean renderSkin(Object skinobj, Object paramobj) { + public boolean renderSkin(Object skinobj, Object paramobj) + throws UnsupportedEncodingException { Context cx = Context.getCurrentContext(); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); @@ -101,7 +103,8 @@ public class JavaObject extends NativeJavaObject { * * @return ... */ - public String renderSkinAsString(Object skinobj, Object paramobj) { + public String renderSkinAsString(Object skinobj, Object paramobj) + throws UnsupportedEncodingException { Context cx = Context.getCurrentContext(); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); @@ -136,7 +139,7 @@ public class JavaObject extends NativeJavaObject { * * @return ... */ - public Object href(Object action) { + public Object href(Object action) throws UnsupportedEncodingException { if (javaObject == null) { return null; } diff --git a/src/helma/scripting/rhino/PathWrapper.java b/src/helma/scripting/rhino/PathWrapper.java index 1cc652bb..e79d6caa 100644 --- a/src/helma/scripting/rhino/PathWrapper.java +++ b/src/helma/scripting/rhino/PathWrapper.java @@ -19,6 +19,8 @@ package helma.scripting.rhino; import helma.framework.core.RequestPath; import org.mozilla.javascript.*; +import java.io.UnsupportedEncodingException; + /** * This class wraps around instances of helma.framework.core.RequestPath and * exposes them in an array-like fashion to the JavaScript runtime. @@ -119,7 +121,7 @@ public class PathWrapper extends ScriptableObject { /** * Returns the wrapped path rendered as URL path. */ - public String href(Object action) { + public String href(Object action) throws UnsupportedEncodingException { if (action != null && action != Undefined.instance) { return path.href(action.toString()); } diff --git a/src/helma/scripting/rhino/RhinoCore.java b/src/helma/scripting/rhino/RhinoCore.java index 1507a7f3..4d405e99 100644 --- a/src/helma/scripting/rhino/RhinoCore.java +++ b/src/helma/scripting/rhino/RhinoCore.java @@ -660,8 +660,8 @@ public final class RhinoCore { return esn; } - protected String postProcessHref(Object obj, String protoName, String basicHref) { - + protected String postProcessHref(Object obj, String protoName, String basicHref) + throws UnsupportedEncodingException { // check if the app.properties specify a href-function to post-process the // basic href. String hrefFunction = app.getProperty("hrefFunction", null); diff --git a/src/helma/servlet/AbstractServletClient.java b/src/helma/servlet/AbstractServletClient.java index d64eeb1e..89a131d3 100644 --- a/src/helma/servlet/AbstractServletClient.java +++ b/src/helma/servlet/AbstractServletClient.java @@ -27,6 +27,8 @@ import java.util.*; import javax.servlet.*; import javax.servlet.http.*; +import org.apache.commons.codec.DecoderException; + /** * This is an abstract Hop servlet adapter. This class communicates with hop applications * via RMI. Subclasses are either one servlet per app, or one servlet that handles multiple apps @@ -49,7 +51,7 @@ public abstract class AbstractServletClient extends HttpServlet { String cookieDomain; // default encoding for requests - String defaultEncoding; + String defaultEncoding = "ISO8859_1"; // allow caching of responses boolean caching; @@ -80,7 +82,12 @@ public abstract class AbstractServletClient extends HttpServlet { } // get default encoding - defaultEncoding = init.getInitParameter("charset"); + String encoding = init.getInitParameter("charset"); + + if (encoding != null) { + defaultEncoding = encoding; + } + debug = ("true".equalsIgnoreCase(init.getInitParameter("debug"))); caching = !("false".equalsIgnoreCase(init.getInitParameter("caching"))); } @@ -125,7 +132,7 @@ public abstract class AbstractServletClient extends HttpServlet { if (encoding == null) { // no encoding from request, use standard one - encoding = defaultEncoding != null ? defaultEncoding : "ISO-8859-1"; + encoding = defaultEncoding; } // read and set http parameters @@ -346,15 +353,6 @@ public abstract class AbstractServletClient extends HttpServlet { res.setDateHeader("Last-Modified", System.currentTimeMillis()); } - // if we don't know which charset to use for parsing HTTP params, - // take the one from the response. This usually works because - // browsers send parameters in the same encoding as the page - // containing the form has. Problem is we can do this only per servlet, - // not per session or even per page, which would produce too much overhead - if (defaultEncoding == null) { - defaultEncoding = hopres.charset; - } - res.setContentLength(hopres.getContentLength()); res.setContentType(hopres.getContentType()); @@ -711,7 +709,8 @@ public abstract class AbstractServletClient extends HttpServlet { return false; } - String getPathInfo(HttpServletRequest req) { + String getPathInfo(HttpServletRequest req) + throws DecoderException, UnsupportedEncodingException { StringTokenizer t = new StringTokenizer(req.getContextPath(), "/"); int prefixTokens = t.countTokens(); @@ -735,11 +734,7 @@ public abstract class AbstractServletClient extends HttpServlet { pathbuffer.append('/'); } - if ((token.indexOf('+') == -1) && (token.indexOf('%') == -1)) { - pathbuffer.append(token); - } else { - pathbuffer.append(URLDecoder.decode(token)); - } + pathbuffer.append(UrlEncoded.smartDecode(token, defaultEncoding)); } // append trailing "/" if it is contained in original URI diff --git a/src/helma/util/UrlEncoded.java b/src/helma/util/UrlEncoded.java index bc1db426..539558c1 100644 --- a/src/helma/util/UrlEncoded.java +++ b/src/helma/util/UrlEncoded.java @@ -16,35 +16,46 @@ package helma.util; -import java.net.URLEncoder; +import java.io.UnsupportedEncodingException; +import org.apache.commons.codec.net.URLCodec; +import org.apache.commons.codec.DecoderException; + +import java.util.BitSet; /** - * A proxy to java.net.URLEncoder which only encodes when - * there is actual work to do. This is necessary because - * URLEncoder is quite inefficient (e.g. it preallocates - * buffers and stuff), and we call it often with - * short string that don't need encoding. + * A subclass of Jakarta Commons Codec URLCodec that offers + * lazy encode/decode methods that only returns a new String + * if actual work had to be done for encoding/decoding. + * This is because URLCodec is a bit inefficient (e.g. it + * preallocates buffers and stuff for each call to encode/decode), + * and we call it often with short strings that don't need encoding. */ -public final class UrlEncoded { +public final class UrlEncoded extends URLCodec { + + public static final String defaultEncoding = "ISO8859_1"; + /** + * URL-encode a string using the given encoding, or return it + * unchanged if no encoding was necessary. * - * - * @param str ... - * - * @return ... + * @param str The string to be URL-encoded + * @param encoding the encoding to use + * @return the URL-encoded string, or str if no encoding necessary */ - public static String encode(String str) { + public static String smartEncode(String str, String encoding) + throws UnsupportedEncodingException { int l = str.length(); boolean needsSpaceEncoding = false; + BitSet urlsafe = WWW_FORM_URL; for (int i = 0; i < l; i++) { char c = str.charAt(i); if (c == ' ') { needsSpaceEncoding = true; - } else if (!(((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) || - ((c >= '0') && (c <= '9')))) { - return URLEncoder.encode(str); + } else if (!urlsafe.get(c)) { + URLCodec codec = new URLCodec(); + return codec.encode(str, encoding); } } @@ -54,4 +65,22 @@ public final class UrlEncoded { return str; } + + /** + * URL-decode a string using the given encoding, + * or return it unchanged if no encoding was necessary. + * + * @param str The string to be URL-decoded + * @param encoding the encoding to use + * @return the URL-decoded string, or str if no decoding necessary + */ + public static String smartDecode(String str, String encoding) + throws DecoderException, UnsupportedEncodingException { + if ((str.indexOf('+') == -1) && (str.indexOf('%') == -1)) { + return str; + } else { + return new URLCodec().decode(str, encoding); + } + } + }