diff --git a/src/main/java/org/json/Cookie.java b/src/main/java/org/json/Cookie.java index 5da423a..ddd1c69 100644 --- a/src/main/java/org/json/Cookie.java +++ b/src/main/java/org/json/Cookie.java @@ -27,6 +27,7 @@ SOFTWARE. /** * Convert a web browser cookie specification to a JSONObject and back. * JSON and Cookies are both notations for name/value pairs. + * See also: https://tools.ietf.org/html/rfc6265 * @author JSON.org * @version 2015-12-09 */ @@ -65,10 +66,11 @@ public class Cookie { /** * Convert a cookie specification string into a JSONObject. The string - * will contain a name value pair separated by '='. The name and the value + * must contain a name value pair separated by '='. The name and the value * will be unescaped, possibly converting '+' and '%' sequences. The * cookie properties may follow, separated by ';', also represented as - * name=value (except the secure property, which does not have a value). + * name=value (except the Attribute properties like "Secure" or "HttpOnly", + * which do not have a value. The value {@link Boolean#TRUE} will be used for these). * The name will be stored under the key "name", and the value will be * stored under the key "value". This method does not do checking or * validation of the parameters. It only converts the cookie string into @@ -76,30 +78,51 @@ public class Cookie { * @param string The cookie specification string. * @return A JSONObject containing "name", "value", and possibly other * members. - * @throws JSONException if a called function fails or a syntax error + * @throws JSONException If there is an error parsing the Cookie String. + * Cookie strings must have at least one '=' character and the 'name' + * portion of the cookie must not be blank. */ - public static JSONObject toJSONObject(String string) throws JSONException { + public static JSONObject toJSONObject(String string) { + final JSONObject jo = new JSONObject(); String name; - JSONObject jo = new JSONObject(); Object value; + + JSONTokener x = new JSONTokener(string); - jo.put("name", x.nextTo('=')); + + name = unescape(x.nextTo('=').trim()); + //per RFC6265, if the name is blank, the cookie should be ignored. + if("".equals(name)) { + throw new JSONException("Cookies must have a 'name'"); + } + jo.put("name", name); + // per RFC6265, if there is no '=', the cookie should be ignored. + // the 'next' call here throws an exception if the '=' is not found. x.next('='); - jo.put("value", x.nextTo(';')); + jo.put("value", unescape(x.nextTo(';')).trim()); + // discard the ';' x.next(); + // parse the remaining cookie attributes while (x.more()) { - name = unescape(x.nextTo("=;")); + name = unescape(x.nextTo("=;")).trim(); + // don't allow a cookies attributes to overwrite it's name or value. + if("name".equalsIgnoreCase(name)) { + throw new JSONException("Illegal attribute name: 'name'"); + } + if("value".equalsIgnoreCase(name)) { + throw new JSONException("Illegal attribute name: 'value'"); + } + // check to see if it's a flag property if (x.next() != '=') { - if (name.equals("secure")) { - value = Boolean.TRUE; - } else { - throw x.syntaxError("Missing '=' in cookie parameter."); - } + value = Boolean.TRUE; } else { - value = unescape(x.nextTo(';')); + value = unescape(x.nextTo(';')).trim(); x.next(); } - jo.put(name, value); + // only store non-blank attributes + if(!"".equals(name) && !"".equals(value)) { + jo.put(name, value); + } } return jo; } @@ -108,9 +131,10 @@ public class Cookie { /** * Convert a JSONObject into a cookie specification string. The JSONObject * must contain "name" and "value" members. - * If the JSONObject contains "expires", "domain", "path", or "secure" - * members, they will be appended to the cookie specification string. - * All other members are ignored. + * If the JSONObject contains other members, they will be appended to the cookie + * specification string. User-Agents are instructed to ignore unknown attributes, + * so ensure your JSONObject is using only known attributes. + * See also: https://tools.ietf.org/html/rfc6265 * @param jo A JSONObject * @return A cookie specification string * @throws JSONException if a called function fails @@ -121,21 +145,21 @@ public class Cookie { sb.append(escape(jo.getString("name"))); sb.append("="); sb.append(escape(jo.getString("value"))); - if (jo.has("expires")) { - sb.append(";expires="); - sb.append(jo.getString("expires")); - } - if (jo.has("domain")) { - sb.append(";domain="); - sb.append(escape(jo.getString("domain"))); - } - if (jo.has("path")) { - sb.append(";path="); - sb.append(escape(jo.getString("path"))); - } - if (jo.optBoolean("secure")) { - sb.append(";secure"); + + for(String key : jo.keySet()){ + if("name".equalsIgnoreCase(key) + || "value".equalsIgnoreCase(key)) { + // already processed above + continue; + } + Object value = jo.opt(key); + if(value instanceof Boolean) { + sb.append(';').append(key); + } else { + sb.append(';').append(key).append('=').append(escape(value.toString())); + } } + return sb.toString(); } diff --git a/src/test/java/org/json/junit/CookieTest.java b/src/test/java/org/json/junit/CookieTest.java index 74756aa..fc29391 100644 --- a/src/test/java/org/json/junit/CookieTest.java +++ b/src/test/java/org/json/junit/CookieTest.java @@ -79,16 +79,12 @@ public class CookieTest { * Expects a JSONException. */ @Test - public void malFormedAttributeException() { + public void booleanAttribute() { String cookieStr = "this=Cookie;myAttribute"; - try { - Cookie.toJSONObject(cookieStr); - fail("Expecting an exception"); - } catch (JSONException e) { - assertEquals("Expecting an exception message", - "Missing '=' in cookie parameter. at 23 [character 24 line 1]", - e.getMessage()); - } + JSONObject jo = Cookie.toJSONObject(cookieStr); + assertTrue("has key 'name'", jo.has("name")); + assertTrue("has key 'value'", jo.has("value")); + assertTrue("has key 'myAttribute'", jo.has("myAttribute")); } /** @@ -104,7 +100,25 @@ public class CookieTest { fail("Expecting an exception"); } catch (JSONException e) { assertEquals("Expecting an exception message", - "Expected '=' and instead saw '' at 0 [character 1 line 1]", + "Cookies must have a 'name'", + e.getMessage()); + } + } + /** + * + * Attempts to create a JSONObject from an cookie string where the name is blank.
+ * Note: Cookie throws an exception, but CookieList does not.
+ * Expects a JSONException + */ + @Test + public void emptyNameCookieException() { + String cookieStr = " = value "; + try { + Cookie.toJSONObject(cookieStr); + fail("Expecting an exception"); + } catch (JSONException e) { + assertEquals("Expecting an exception message", + "Cookies must have a 'name'", e.getMessage()); } } @@ -149,8 +163,8 @@ public class CookieTest { } /** - * Cookie.toString() will omit the non-standard "thiswont=beIncluded" - * attribute, but the attribute is still stored in the JSONObject. + * Cookie.toString() will emit the non-standard "thiswont=beIncluded" + * attribute, and the attribute is still stored in the JSONObject. * This test confirms both behaviors. */ @Test @@ -163,15 +177,15 @@ public class CookieTest { "thisWont=beIncluded;"+ "secure"; String expectedCookieStr = - "{\"path\":\"/\","+ + "{\"thisWont\":\"beIncluded\","+ + "\"path\":\"/\","+ "\"expires\":\"Wed, 19-Mar-2014 17:53:53 GMT\","+ "\"domain\":\".yahoo.com\","+ "\"name\":\"PH\","+ "\"secure\":true,"+ "\"value\":\"deleted\"}"; // Add the nonstandard attribute to the expected cookie string - String expectedDirectCompareCookieStr = - expectedCookieStr.replaceAll("\\{", "\\{\"thisWont\":\"beIncluded\","); + String expectedDirectCompareCookieStr = expectedCookieStr; // convert all strings into JSONObjects JSONObject jsonObject = Cookie.toJSONObject(cookieStr); JSONObject expectedJsonObject = new JSONObject(expectedCookieStr);