views:

1304

answers:

6

Hello all,

My application needs to store the users email address in a cookie so that I can pre-populate a login form (username == email address). I set the cookie value in JavaScript. If I read it from JavaScript, I get "[email protected]". If I look at it in the cookie viewer in Firefox I get "[email protected]".

When I try to read it on the server-side in Java however, I only get "foo".

Do I need to do some sort of encoding/decoding here? If so, how do I do it in a way that can be decoded by both JavaScript and Java?

Thanks in advance! -Michael

A: 

You need to escape the value part of your cookie.

document.cookie = name + "=" +escape( value ) 
    + ( ( expires ) ? ";expires=" + expires_date.toGMTString() : "" )
    + ( ( path ) ? ";path=" + path : "" ) 
    + ( ( domain ) ? ";domain=" + domain : "" ) 
    + ( ( secure ) ? ";secure" : "" );
Glen
A: 

Thanks for the quick reply, but that didn't seem to work...

According to http://www.w3schools.com/jsref/jsref_escape.asp the @ symbol isn't escaped.

Seems like storing an email address in a cookie would be a common thing to do...

bowmanmc
+4  A: 

From the javax.servlet.http.Cookie doco for setValue(String):

Assigns a new value to a cookie after the cookie is created. If you use a binary value, you may want to use BASE64 encoding.

With Version 0 cookies, values should not contain white space, brackets, parentheses, equals signs, commas, double quotes, slashes, question marks, at signs, colons, and semicolons. Empty values may not behave the same way on all browsers.

I'm guessing you need to BASE64 encode it on the way in (via JavaScript) and on the way out (via Java)

Martin
Please see post below about equals sign... (what's with the 300 char limit on comments?)
bowmanmc
+1  A: 

We're getting closer... I think ;-)

"[email protected]" encodes to Zm9vQGJhci5jb20= using the base64 encoder I lifted from http://extjs.com/forum/showthread.php?p=167166 (I'm using ExtJs)

Viewing the cookie in Firefox looks correct, and decoding in JavaScript works but on the Java side I am only getting Zm9vQGJhci5jb20 (note the missing = sign at the end)

Can I get away with just appending an = character to the end of all encoded strings on the Java side or could there be more than one = sign in the encoded string?

Thanks,

-Michael

bowmanmc
You can't just blindly append equals signs. Base64-encoded strings have either 0, 1, or 2 equals signs to pad the number of characters to a multiple of 4. http://en.wikipedia.org/wiki/Base64
Quinn Taylor
A: 

Ok, I have found two solutions to this. Here is the first one. Please vote up the solution you like best!

Add back in padding to Base64 encoded strings. Inspiration for this came from http://fi.am/entry/urlsafe-base64-encodingdecoding-in-two-lines/

In this solution, the JavaScript stays the same (base64 encode everything) and the server side looks like:

public class CookieDecoder {

private static final Log log = LogFactory.getLog(CookieDecoder.class);

/**
 * @param cookieValue The value of the cookie to decode
 * @return Returns the decoded string
 */
public String decode(String cookieValue) {
    if (cookieValue == null || "".equals(cookieValue)) {
        return null;
    }

    if (!cookieValue.endsWith("=")) {
        cookieValue = padString(cookieValue);
    }

    if (log.isDebugEnabled()) {
        log.debug("Decoding string: " + cookieValue);
    }

    Base64 base64 = new Base64();
    byte[] encodedBytes = cookieValue.getBytes();
    byte[] decodedBytes = base64.decode(encodedBytes);
    String result = new String(decodedBytes);
    if (log.isDebugEnabled()) {
        log.debug("Decoded string to: " + result);
    }
    return result;
}

private String padString(String value) {
    int mod = value.length() % 4;
    if (mod <= 0) {
        return value;
    }
    int numEqs = 4 - mod;
    if (log.isDebugEnabled()) {
        log.debug("Padding value with " + numEqs + " = signs");
    }
    for (int i = 0; i < numEqs; i++) {
        value += "=";
    }
    return value;
}

}

On the JavaScript side, you just need to make sure you base64 encode the values:

var encodedValue = this.base64.encode(value);
document.cookie = name + "=" + encodedValue + 
                  "; expires=" + this.expires.toGMTString() + 
                  "; path=" + this.path;
bowmanmc
A: 

The second solution is just to URLEncode the Base64 encoded string. I'm using commons codec to do the encoding here. Please vote up which solution you think is the best!

Java Code:

public class CookieDecoder {

    private static final Log log = LogFactory.getLog(CookieDecoder.class);

    /**
     * @param cookieValue The value of the cookie to decode
     * @return Returns the decoded string
     */
    public String decode(String cookieValue) {
        if (cookieValue == null || "".equals(cookieValue)) {
            return null;
        }

        if (log.isDebugEnabled()) {
            log.debug("Decoding string: " + cookieValue);
        }
        URLCodec urlCodec = new URLCodec();
        String b64Str;
        try {
            b64Str = urlCodec.decode(cookieValue);
        }
        catch (DecoderException e) {
            log.error("Error decoding string: " + cookieValue);
            return null;
        }
        Base64 base64 = new Base64();
        byte[] encodedBytes = b64Str.getBytes();
        byte[] decodedBytes = base64.decode(encodedBytes);
        String result = new String(decodedBytes);
        if (log.isDebugEnabled()) {
            log.debug("Decoded string to: " + result);
        }
        return result;
    }
}

But now I have to decode it on the JavaScript side as well... Encode:

var encodedValue = this.base64.encode(value); document.cookie = name + "=" + escape(encodedValue) + "; expires=" + this.expires.toGMTString() + "; path=" + this.path;

Decode:

var nameEQ = name + "="; var ca = document.cookie.split(';'); for(var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0)==' ') { c = c.substring(1,c.length); } if (c.indexOf(nameEQ) == 0) { var encodedValue = c.substring(nameEQ.length,c.length); return this.base64.decode(unescape(encodedValue)); } } return null;

bowmanmc