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.
This commit is contained in:
hns 2004-09-06 13:48:30 +00:00
parent e3eed21f50
commit b8dd92a125
10 changed files with 108 additions and 63 deletions

View file

@ -602,7 +602,7 @@ public final class Application implements IPathElement, Runnable {
try { try {
res.close(charset); res.close(charset);
} catch (UnsupportedEncodingException uee) { } catch (UnsupportedEncodingException uee) {
logEvent("Unsupported response encoding: " + uee.getMessage()); logError("Unsupported response encoding", uee);
} }
} else { } else {
res.waitForClose(); res.waitForClose();
@ -692,9 +692,9 @@ public final class Application implements IPathElement, Runnable {
String rootFactory = classMapping.getProperty("root.factory.class"); String rootFactory = classMapping.getProperty("root.factory.class");
Class c = typemgr.getClassLoader().loadClass(rootFactory); Class c = typemgr.getClassLoader().loadClass(rootFactory);
Method m = c.getMethod(classMapping.getProperty("root.factory.method"), Method m = c.getMethod(classMapping.getProperty("root.factory.method"),
null); (Class[]) null);
rootObject = m.invoke(c, null); rootObject = m.invoke(c, (Object[]) null);
} else { } else {
String rootClass = classMapping.getProperty("root"); String rootClass = classMapping.getProperty("root");
Class c = typemgr.getClassLoader().loadClass(rootClass); 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. * Return the href to the root of this application.
*/ */
public String getRootHref() { public String getRootHref() throws UnsupportedEncodingException {
return getNodeHref(getDataRoot(), null); return getNodeHref(getDataRoot(), null);
} }
/** /**
* Return a path to be used in a URL pointing to the given element and action * 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); StringBuffer b = new StringBuffer(baseURI);
composeHref(elem, b, 0); composeHref(elem, b, 0);
if (actionName != null) { if (actionName != null) {
b.append(UrlEncoded.encode(actionName)); b.append(UrlEncoded.smartEncode(actionName, charset));
} }
return b.toString(); 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)) { if ((elem == null) || (pathCount > 50)) {
return; return;
} }
@ -1096,7 +1098,7 @@ public final class Application implements IPathElement, Runnable {
// append ourselves // append ourselves
String ename = getElementName(elem); String ename = getElementName(elem);
if (ename != null) { if (ename != null) {
b.append(UrlEncoded.encode(ename)); b.append(UrlEncoded.smartEncode(ename, charset));
b.append("/"); b.append("/");
} }
} }

View file

@ -17,6 +17,8 @@
package helma.framework.core; package helma.framework.core;
import java.util.*; import java.util.*;
import java.io.UnsupportedEncodingException;
import helma.util.UrlEncoded; import helma.util.UrlEncoded;
/** /**
@ -106,7 +108,7 @@ public class RequestPath {
/** /**
* Returns the string representation of this path usable for links. * 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()); StringBuffer buffer = new StringBuffer(app.getBaseURI());
int start = 1; int start = 1;
@ -121,12 +123,12 @@ public class RequestPath {
} }
for (int i=start; i<ids.size(); i++) { for (int i=start; i<ids.size(); i++) {
buffer.append(UrlEncoded.encode(ids.get(i).toString())); buffer.append(UrlEncoded.smartEncode(ids.get(i).toString(), app.charset));
buffer.append("/"); buffer.append("/");
} }
if (action != null) { if (action != null) {
buffer.append(UrlEncoded.encode(action)); buffer.append(UrlEncoded.smartEncode(action, app.charset));
} }
return buffer.toString(); return buffer.toString();

View file

@ -23,6 +23,7 @@ import helma.util.SystemMap;
import helma.util.WrappedMap; import helma.util.WrappedMap;
import helma.util.UrlEncoded; import helma.util.UrlEncoded;
import java.util.*; import java.util.*;
import java.io.UnsupportedEncodingException;
/** /**
* This represents a Helma skin, i.e. a template created from containing Macro tags * This represents a Helma skin, i.e. a template created from containing Macro tags
@ -119,7 +120,7 @@ public final class Skin {
* Render this skin * Render this skin
*/ */
public void render(RequestEvaluator reval, Object thisObject, Map paramObject) public void render(RequestEvaluator reval, Object thisObject, Map paramObject)
throws RedirectException { throws RedirectException, UnsupportedEncodingException {
// check for endless skin recursion // check for endless skin recursion
if (++reval.skinDepth > 50) { if (++reval.skinDepth > 50) {
throw new RuntimeException("Recursive skin invocation suspected"); throw new RuntimeException("Recursive skin invocation suspected");
@ -369,7 +370,8 @@ public final class Skin {
* Render the macro given a handler object * Render the macro given a handler object
*/ */
public void render(RequestEvaluator reval, Object thisObject, Map paramObject, public void render(RequestEvaluator reval, Object thisObject, Map paramObject,
Map handlerCache) throws RedirectException { Map handlerCache)
throws RedirectException, UnsupportedEncodingException {
if ((sandbox != null) && !sandbox.contains(fullName)) { if ((sandbox != null) && !sandbox.contains(fullName)) {
//String h = (handler == null) ? "global" : handler; //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; Object value = null;
if ("message".equals(name)) { if ("message".equals(name)) {
@ -563,7 +566,8 @@ public final class Skin {
writeResponse(value, reval.res.getBuffer(), true); writeResponse(value, reval.res.getBuffer(), true);
} }
private void renderFromRequest(RequestEvaluator reval) { private void renderFromRequest(RequestEvaluator reval)
throws UnsupportedEncodingException {
if (reval.req == null) { if (reval.req == null) {
return; return;
} }
@ -573,7 +577,8 @@ public final class Skin {
writeResponse(value, reval.res.getBuffer(), true); writeResponse(value, reval.res.getBuffer(), true);
} }
private void renderFromSession(RequestEvaluator reval) { private void renderFromSession(RequestEvaluator reval)
throws UnsupportedEncodingException {
if (reval.session == null) { if (reval.session == null) {
return; return;
} }
@ -583,7 +588,8 @@ public final class Skin {
writeResponse(value, reval.res.getBuffer(), true); 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) { if (paramObject == null) {
reval.res.write("[HopMacro error: Skin requires a parameter object]"); reval.res.write("[HopMacro error: Skin requires a parameter object]");
} else { } else {
@ -596,7 +602,8 @@ public final class Skin {
/** /**
* Utility method for writing text out to the response object. * 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; String text;
if (value == null) { if (value == null) {
@ -650,7 +657,7 @@ public final class Skin {
break; break;
case ENCODE_URL: case ENCODE_URL:
buffer.append(UrlEncoded.encode(text)); buffer.append(UrlEncoded.smartEncode(text, app.charset));
break; break;

View file

@ -89,7 +89,8 @@ public class GlobalObject extends ImporterTopLevel {
* *
* @return ... * @return ...
*/ */
public boolean renderSkin(Object skinobj, Object paramobj) { public boolean renderSkin(Object skinobj, Object paramobj)
throws UnsupportedEncodingException {
Context cx = Context.getCurrentContext(); Context cx = Context.getCurrentContext();
RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval");
RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine");
@ -122,7 +123,8 @@ public class GlobalObject extends ImporterTopLevel {
* *
* @return ... * @return ...
*/ */
public String renderSkinAsString(Object skinobj, Object paramobj) { public String renderSkinAsString(Object skinobj, Object paramobj)
throws UnsupportedEncodingException {
Context cx = Context.getCurrentContext(); Context cx = Context.getCurrentContext();
RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval");
RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine");

View file

@ -29,6 +29,7 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Map; import java.util.Map;
import java.io.UnsupportedEncodingException;
/** /**
* *
@ -247,7 +248,8 @@ public class HopObject extends ScriptableObject implements Wrapper {
* *
* @return ... * @return ...
*/ */
public boolean jsFunction_renderSkin(Object skinobj, Object paramobj) { public boolean jsFunction_renderSkin(Object skinobj, Object paramobj)
throws UnsupportedEncodingException {
Context cx = Context.getCurrentContext(); Context cx = Context.getCurrentContext();
RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval");
RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine");
@ -280,7 +282,8 @@ public class HopObject extends ScriptableObject implements Wrapper {
* *
* @return ... * @return ...
*/ */
public String jsFunction_renderSkinAsString(Object skinobj, Object paramobj) { public String jsFunction_renderSkinAsString(Object skinobj, Object paramobj)
throws UnsupportedEncodingException {
Context cx = Context.getCurrentContext(); Context cx = Context.getCurrentContext();
RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval");
RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine");
@ -315,7 +318,7 @@ public class HopObject extends ScriptableObject implements Wrapper {
* *
* @return ... * @return ...
*/ */
public Object jsFunction_href(Object action) { public Object jsFunction_href(Object action) throws UnsupportedEncodingException {
if (node == null) { if (node == null) {
return null; return null;
} }

View file

@ -21,6 +21,7 @@ import org.mozilla.javascript.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.io.UnsupportedEncodingException;
/** /**
* *
@ -68,7 +69,8 @@ public class JavaObject extends NativeJavaObject {
* *
* @return ... * @return ...
*/ */
public boolean renderSkin(Object skinobj, Object paramobj) { public boolean renderSkin(Object skinobj, Object paramobj)
throws UnsupportedEncodingException {
Context cx = Context.getCurrentContext(); Context cx = Context.getCurrentContext();
RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval");
RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine");
@ -101,7 +103,8 @@ public class JavaObject extends NativeJavaObject {
* *
* @return ... * @return ...
*/ */
public String renderSkinAsString(Object skinobj, Object paramobj) { public String renderSkinAsString(Object skinobj, Object paramobj)
throws UnsupportedEncodingException {
Context cx = Context.getCurrentContext(); Context cx = Context.getCurrentContext();
RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval"); RequestEvaluator reval = (RequestEvaluator) cx.getThreadLocal("reval");
RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine"); RhinoEngine engine = (RhinoEngine) cx.getThreadLocal("engine");
@ -136,7 +139,7 @@ public class JavaObject extends NativeJavaObject {
* *
* @return ... * @return ...
*/ */
public Object href(Object action) { public Object href(Object action) throws UnsupportedEncodingException {
if (javaObject == null) { if (javaObject == null) {
return null; return null;
} }

View file

@ -19,6 +19,8 @@ package helma.scripting.rhino;
import helma.framework.core.RequestPath; import helma.framework.core.RequestPath;
import org.mozilla.javascript.*; import org.mozilla.javascript.*;
import java.io.UnsupportedEncodingException;
/** /**
* This class wraps around instances of helma.framework.core.RequestPath and * This class wraps around instances of helma.framework.core.RequestPath and
* exposes them in an array-like fashion to the JavaScript runtime. * 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. * 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) { if (action != null && action != Undefined.instance) {
return path.href(action.toString()); return path.href(action.toString());
} }

View file

@ -660,8 +660,8 @@ public final class RhinoCore {
return esn; 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 // check if the app.properties specify a href-function to post-process the
// basic href. // basic href.
String hrefFunction = app.getProperty("hrefFunction", null); String hrefFunction = app.getProperty("hrefFunction", null);

View file

@ -27,6 +27,8 @@ import java.util.*;
import javax.servlet.*; import javax.servlet.*;
import javax.servlet.http.*; import javax.servlet.http.*;
import org.apache.commons.codec.DecoderException;
/** /**
* This is an abstract Hop servlet adapter. This class communicates with hop applications * 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 * 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; String cookieDomain;
// default encoding for requests // default encoding for requests
String defaultEncoding; String defaultEncoding = "ISO8859_1";
// allow caching of responses // allow caching of responses
boolean caching; boolean caching;
@ -80,7 +82,12 @@ public abstract class AbstractServletClient extends HttpServlet {
} }
// get default encoding // get default encoding
defaultEncoding = init.getInitParameter("charset"); String encoding = init.getInitParameter("charset");
if (encoding != null) {
defaultEncoding = encoding;
}
debug = ("true".equalsIgnoreCase(init.getInitParameter("debug"))); debug = ("true".equalsIgnoreCase(init.getInitParameter("debug")));
caching = !("false".equalsIgnoreCase(init.getInitParameter("caching"))); caching = !("false".equalsIgnoreCase(init.getInitParameter("caching")));
} }
@ -125,7 +132,7 @@ public abstract class AbstractServletClient extends HttpServlet {
if (encoding == null) { if (encoding == null) {
// no encoding from request, use standard one // no encoding from request, use standard one
encoding = defaultEncoding != null ? defaultEncoding : "ISO-8859-1"; encoding = defaultEncoding;
} }
// read and set http parameters // read and set http parameters
@ -346,15 +353,6 @@ public abstract class AbstractServletClient extends HttpServlet {
res.setDateHeader("Last-Modified", System.currentTimeMillis()); 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.setContentLength(hopres.getContentLength());
res.setContentType(hopres.getContentType()); res.setContentType(hopres.getContentType());
@ -711,7 +709,8 @@ public abstract class AbstractServletClient extends HttpServlet {
return false; return false;
} }
String getPathInfo(HttpServletRequest req) { String getPathInfo(HttpServletRequest req)
throws DecoderException, UnsupportedEncodingException {
StringTokenizer t = new StringTokenizer(req.getContextPath(), "/"); StringTokenizer t = new StringTokenizer(req.getContextPath(), "/");
int prefixTokens = t.countTokens(); int prefixTokens = t.countTokens();
@ -735,11 +734,7 @@ public abstract class AbstractServletClient extends HttpServlet {
pathbuffer.append('/'); pathbuffer.append('/');
} }
if ((token.indexOf('+') == -1) && (token.indexOf('%') == -1)) { pathbuffer.append(UrlEncoded.smartDecode(token, defaultEncoding));
pathbuffer.append(token);
} else {
pathbuffer.append(URLDecoder.decode(token));
}
} }
// append trailing "/" if it is contained in original URI // append trailing "/" if it is contained in original URI

View file

@ -16,35 +16,46 @@
package helma.util; 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 * A subclass of Jakarta Commons Codec URLCodec that offers
* there is actual work to do. This is necessary because * lazy encode/decode methods that only returns a new String
* URLEncoder is quite inefficient (e.g. it preallocates * if actual work had to be done for encoding/decoding.
* buffers and stuff), and we call it often with * This is because URLCodec is a bit inefficient (e.g. it
* short string that don't need encoding. * 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 The string to be URL-encoded
* @param str ... * @param encoding the encoding to use
* * @return the URL-encoded string, or str if no encoding necessary
* @return ...
*/ */
public static String encode(String str) { public static String smartEncode(String str, String encoding)
throws UnsupportedEncodingException {
int l = str.length(); int l = str.length();
boolean needsSpaceEncoding = false; boolean needsSpaceEncoding = false;
BitSet urlsafe = WWW_FORM_URL;
for (int i = 0; i < l; i++) { for (int i = 0; i < l; i++) {
char c = str.charAt(i); char c = str.charAt(i);
if (c == ' ') { if (c == ' ') {
needsSpaceEncoding = true; needsSpaceEncoding = true;
} else if (!(((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) || } else if (!urlsafe.get(c)) {
((c >= '0') && (c <= '9')))) { URLCodec codec = new URLCodec();
return URLEncoder.encode(str); return codec.encode(str, encoding);
} }
} }
@ -54,4 +65,22 @@ public final class UrlEncoded {
return str; 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);
}
}
} }