views:

410

answers:

2

The login page in my Tapestry application has a property in which the password the user types in is stored, which is then compared against the value from the database. If the user enters a password with multi-byte characters, such as:

áéíóú

...an inspection of the return value of getPassword() (the abstract method for the corresponding property) gives:

áéíóú

Clearly, that's not encoded properly. Yet Firebug reports that the page is served up in UTF-8, so presumably the form submission request would also be encoded in UTF-8. Inspecting the value as it comes from the database produces the correct string, so it wouldn't appear to be an OS or IDE encoding issue. I have not overridden Tapestry's default value for org.apache.tapestry.output-encoding in the .application file, and the Tapestry 4 documentation indicates that the default value for the property is UTF-8.

So why does Tapestry appear to botch the encoding when setting the property?

Relevant code follows:

Login.html

<html jwcid="@Shell" doctype='html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"' ...>
    <body jwcid="@Body">
        ...
        <form jwcid="@Form" listener="listener:attemptLogin" ...>
            ...
            <input jwcid="password"/>
            ...
        </form>
        ...
     </body>
</html>

Login.page

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification
    PUBLIC "-//Apache Software Foundation//Tapestry Specification 4.0//EN"
    "http://jakarta.apache.org/tapestry/dtd/Tapestry_4_0.dtd"&gt;

<page-specification class="mycode.Login">
    ...
    <property name="password" />
    ...
    <component id="password" type="TextField">
        <binding name="value" value="password"/>
        <binding name="hidden" value="true"/>
        ...
    </component>
    ...
</page-specification>

Login.java

...
public abstract class Login extends BasePage {
    ...
    public abstract String getPassword();
    ...
    public void attemptLogin() {
        // At this point, inspecting getPassword() returns
        // the incorrectly encoded String.
    }
    ...
}

Updates

@Jan Soltis: Well, if I inspect the value that comes from the database, it displays the correct string, so it would seem that my editor, OS and database are all encoding the value correctly. I've also checked my .application file; it does not contain an org.apache.tapestry.output-encoding entry, and the Tapestry 4 documentation indicates that the default value for this property is UTF-8. I have updated the description above to reflect the answers to your questions.

@myself: Solution found.

+2  A: 

Everything seems to be correct.

Are you really sure getPassword() returns garbage? Isn't it someone else (your editor, OS, database,...) that doesn't know that it's a unicode string when it displays it to you while the password may be perfectly okay? What exactly makes you think it's a garbage?

I would also make sure there's no strange encoding set in the .application config file

<meta key="org.apache.tapestry.output-encoding" value="some strange encoding"/>
Jan Soltis
+2  A: 

I found the problem. Tomcat was mangling the parameters before Tapestry or my page class even had a crack at it. Creating a servlet filter that enforced the desired character encoding fixed it.

CharacterEncodingFilter.java

package mycode;

import java.io.IOException;

import javax.servlet.*;

/**
 * Allows you to enforce a particular character encoding on incoming requests.
 * @author Robert J. Walker
 */
public class CharacterEncodingFilter implements Filter {
    private static final String ENCODINGPARAM = "encoding";

    private String encoding;

    public void init(FilterConfig config) throws ServletException {
     encoding = config.getInitParameter(ENCODINGPARAM);

     if (encoding != null) {
      encoding = encoding.trim();
     }
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
     request.setCharacterEncoding(encoding);
     chain.doFilter(request, response);
    }

    public void destroy() {
     // do nothing
    }
}

web.xml

<web-app>
    ...
    <filter>
        <filter-name>characterEncoding</filter-name>
        <filter-class>mycode.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncoding</filter-name>
        <url-pattern>/app/*</url-pattern>
    </filter-mapping>
    ...
</web-app>
Robert J. Walker