mirror of
https://github.com/ethauvin/JSON-java.git
synced 2025-06-17 07:50:52 -07:00
Updates Cookie class to be a more generic in attribute parsing and emit.
This is so the library can age better as new attributes are added to RFC revisions.
This commit is contained in:
parent
956bdfa5b7
commit
b4a75c7bf8
2 changed files with 85 additions and 47 deletions
|
@ -27,6 +27,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,10 +66,11 @@ 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
|
||||||
|
@ -76,30 +78,51 @@ public class Cookie {
|
||||||
* @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();
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
@ -108,9 +131,10 @@ 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.
|
||||||
* 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 if a called function fails
|
||||||
|
@ -121,21 +145,21 @@ public class Cookie {
|
||||||
sb.append(escape(jo.getString("name")));
|
sb.append(escape(jo.getString("name")));
|
||||||
sb.append("=");
|
sb.append("=");
|
||||||
sb.append(escape(jo.getString("value")));
|
sb.append(escape(jo.getString("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")));
|
}
|
||||||
}
|
Object value = jo.opt(key);
|
||||||
if (jo.has("path")) {
|
if(value instanceof Boolean) {
|
||||||
sb.append(";path=");
|
sb.append(';').append(key);
|
||||||
sb.append(escape(jo.getString("path")));
|
} else {
|
||||||
}
|
sb.append(';').append(key).append('=').append(escape(value.toString()));
|
||||||
if (jo.optBoolean("secure")) {
|
}
|
||||||
sb.append(";secure");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue