1
0
Fork 0
mirror of https://github.com/ethauvin/JSON-java.git synced 2025-06-17 07:50:52 -07:00

Merge pull request #521 from johnjaylward/CookieFlagSupport

Updates Cookie class to be more generic in attribute parsing and emit.
This commit is contained in:
Sean Leary 2020-05-29 09:59:27 -05:00 committed by GitHub
commit fee6ddb922
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 122 additions and 53 deletions

View file

@ -1,5 +1,7 @@
package org.json; package org.json;
import java.util.Locale;
/* /*
Copyright (c) 2002 JSON.org Copyright (c) 2002 JSON.org
@ -27,6 +29,7 @@ SOFTWARE.
/** /**
* Convert a web browser cookie specification to a JSONObject and back. * Convert a web browser cookie specification to a JSONObject and back.
* JSON and Cookies are both notations for name/value pairs. * JSON and Cookies are both notations for name/value pairs.
* See also: <a href="https://tools.ietf.org/html/rfc6265">https://tools.ietf.org/html/rfc6265</a>
* @author JSON.org * @author JSON.org
* @version 2015-12-09 * @version 2015-12-09
*/ */
@ -65,41 +68,65 @@ public class Cookie {
/** /**
* Convert a cookie specification string into a JSONObject. The string * 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 * will be unescaped, possibly converting '+' and '%' sequences. The
* cookie properties may follow, separated by ';', also represented as * 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 * 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 * stored under the key "value". This method does not do checking or
* validation of the parameters. It only converts the cookie string into * validation of the parameters. It only converts the cookie string into
* a JSONObject. * a JSONObject. All attribute names are converted to lower case keys in the
* JSONObject (HttpOnly =&gt; httponly). If an attribute is specified more than
* once, only the value found closer to the end of the cookie-string is kept.
* @param string The cookie specification string. * @param string The cookie specification string.
* @return A JSONObject containing "name", "value", and possibly other * @return A JSONObject containing "name", "value", and possibly other
* members. * 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; String name;
JSONObject jo = new JSONObject();
Object value; Object value;
JSONTokener x = new JSONTokener(string); 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('='); x.next('=');
jo.put("value", x.nextTo(';')); jo.put("value", unescape(x.nextTo(';')).trim());
// discard the ';'
x.next(); x.next();
// parse the remaining cookie attributes
while (x.more()) { while (x.more()) {
name = unescape(x.nextTo("=;")); name = unescape(x.nextTo("=;")).trim().toLowerCase(Locale.ROOT);
// 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 (x.next() != '=') {
if (name.equals("secure")) { value = Boolean.TRUE;
value = Boolean.TRUE;
} else {
throw x.syntaxError("Missing '=' in cookie parameter.");
}
} else { } else {
value = unescape(x.nextTo(';')); value = unescape(x.nextTo(';')).trim();
x.next(); x.next();
} }
jo.put(name, value); // only store non-blank attributes
if(!"".equals(name) && !"".equals(value)) {
jo.put(name, value);
}
} }
return jo; return jo;
} }
@ -107,35 +134,63 @@ public class Cookie {
/** /**
* Convert a JSONObject into a cookie specification string. The JSONObject * Convert a JSONObject into a cookie specification string. The JSONObject
* must contain "name" and "value" members. * must contain "name" and "value" members (case insensitive).
* If the JSONObject contains "expires", "domain", "path", or "secure" * If the JSONObject contains other members, they will be appended to the cookie
* members, they will be appended to the cookie specification string. * specification string. User-Agents are instructed to ignore unknown attributes,
* All other members are ignored. * so ensure your JSONObject is using only known attributes.
* See also: <a href="https://tools.ietf.org/html/rfc6265">https://tools.ietf.org/html/rfc6265</a>
* @param jo A JSONObject * @param jo A JSONObject
* @return A cookie specification string * @return A cookie specification string
* @throws JSONException if a called function fails * @throws JSONException thrown if the cookie has no name.
*/ */
public static String toString(JSONObject jo) throws JSONException { public static String toString(JSONObject jo) throws JSONException {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(escape(jo.getString("name"))); String name = null;
Object value = null;
for(String key : jo.keySet()){
if("name".equalsIgnoreCase(key)) {
name = jo.getString(key).trim();
}
if("value".equalsIgnoreCase(key)) {
value=jo.getString(key).trim();
}
if(name != null && value != null) {
break;
}
}
if(name == null || "".equals(name.trim())) {
throw new JSONException("Cookie does not have a name");
}
if(value == null) {
value = "";
}
sb.append(escape(name));
sb.append("="); sb.append("=");
sb.append(escape(jo.getString("value"))); sb.append(escape((String)value));
if (jo.has("expires")) {
sb.append(";expires="); for(String key : jo.keySet()){
sb.append(jo.getString("expires")); if("name".equalsIgnoreCase(key)
} || "value".equalsIgnoreCase(key)) {
if (jo.has("domain")) { // already processed above
sb.append(";domain="); continue;
sb.append(escape(jo.getString("domain"))); }
} value = jo.opt(key);
if (jo.has("path")) { if(value instanceof Boolean) {
sb.append(";path="); if(Boolean.TRUE.equals(value)) {
sb.append(escape(jo.getString("path"))); sb.append(';').append(escape(key));
} }
if (jo.optBoolean("secure")) { // don't emit false values
sb.append(";secure"); } else {
sb.append(';')
.append(escape(key))
.append('=')
.append(escape(value.toString()));
}
} }
return sb.toString(); return sb.toString();
} }

View file

@ -79,16 +79,12 @@ public class CookieTest {
* Expects a JSONException. * Expects a JSONException.
*/ */
@Test @Test
public void malFormedAttributeException() { public void booleanAttribute() {
String cookieStr = "this=Cookie;myAttribute"; String cookieStr = "this=Cookie;myAttribute";
try { JSONObject jo = Cookie.toJSONObject(cookieStr);
Cookie.toJSONObject(cookieStr); assertTrue("has key 'name'", jo.has("name"));
fail("Expecting an exception"); assertTrue("has key 'value'", jo.has("value"));
} catch (JSONException e) { assertTrue("has key 'myAttribute'", jo.has("myattribute"));
assertEquals("Expecting an exception message",
"Missing '=' in cookie parameter. at 23 [character 24 line 1]",
e.getMessage());
}
} }
/** /**
@ -104,7 +100,25 @@ public class CookieTest {
fail("Expecting an exception"); fail("Expecting an exception");
} catch (JSONException e) { } catch (JSONException e) {
assertEquals("Expecting an exception message", 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.<br>
* Note: Cookie throws an exception, but CookieList does not.<br>
* 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()); e.getMessage());
} }
} }
@ -149,8 +163,8 @@ public class CookieTest {
} }
/** /**
* Cookie.toString() will omit the non-standard "thiswont=beIncluded" * Cookie.toString() will emit the non-standard "thiswont=beIncluded"
* attribute, but the attribute is still stored in the JSONObject. * attribute, and the attribute is still stored in the JSONObject.
* This test confirms both behaviors. * This test confirms both behaviors.
*/ */
@Test @Test
@ -163,15 +177,15 @@ public class CookieTest {
"thisWont=beIncluded;"+ "thisWont=beIncluded;"+
"secure"; "secure";
String expectedCookieStr = String expectedCookieStr =
"{\"path\":\"/\","+ "{\"thiswont\":\"beIncluded\","+
"\"path\":\"/\","+
"\"expires\":\"Wed, 19-Mar-2014 17:53:53 GMT\","+ "\"expires\":\"Wed, 19-Mar-2014 17:53:53 GMT\","+
"\"domain\":\".yahoo.com\","+ "\"domain\":\".yahoo.com\","+
"\"name\":\"PH\","+ "\"name\":\"PH\","+
"\"secure\":true,"+ "\"secure\":true,"+
"\"value\":\"deleted\"}"; "\"value\":\"deleted\"}";
// Add the nonstandard attribute to the expected cookie string // Add the nonstandard attribute to the expected cookie string
String expectedDirectCompareCookieStr = String expectedDirectCompareCookieStr = expectedCookieStr;
expectedCookieStr.replaceAll("\\{", "\\{\"thisWont\":\"beIncluded\",");
// convert all strings into JSONObjects // convert all strings into JSONObjects
JSONObject jsonObject = Cookie.toJSONObject(cookieStr); JSONObject jsonObject = Cookie.toJSONObject(cookieStr);
JSONObject expectedJsonObject = new JSONObject(expectedCookieStr); JSONObject expectedJsonObject = new JSONObject(expectedCookieStr);