content: package org.json;
  
  import java.util.Locale;
  
  /*
  Public Domain.
  */
  
  /**
   * Convert a web browser cookie specification to a JSONObject and back.
   * 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
   * @version 2015-12-09
   */
  public class Cookie {
  
      /**
       * Constructs a new Cookie object.
       */
      public Cookie() {
      }
  
      /**
       * Produce a copy of a string in which the characters '+', '%', '=', ';'
       * and control characters are replaced with "%hh". This is a gentle form
       * of URL encoding, attempting to cause as little distortion to the
       * string as possible. The characters '=' and ';' are meta characters in
       * cookies. By convention, they are escaped using the URL-encoding. This is
       * only a convention, not a standard. Often, cookies are expected to have
       * encoded values. We encode '=' and ';' because we must. We encode '%' and
       * '+' because they are meta characters in URL encoding.
       * @param string The source string.
       * @return       The escaped result.
       */
      public static String escape(String string) {
          char            c;
          String          s = string.trim();
          int             length = s.length();
          StringBuilder   sb = new StringBuilder(length);
          for (int i = 0; i < length; i += 1) {
              c = s.charAt(i);
              if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') {
                  sb.append('%');
                  sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16));
                  sb.append(Character.forDigit((char)(c & 0x0f), 16));
              } else {
                  sb.append(c);
              }
          }
          return sb.toString();
      }
  
  
      /**
       * Convert a cookie specification string into a JSONObject. The string
       * 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 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
       * 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.
       * @return A JSONObject containing "name", "value", and possibly other
       *  members.
       * @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) {
          final JSONObject     jo = new JSONObject();
          String         name;
          Object         value;
          
          
          JSONTokener x = new JSONTokener(string);
          
          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", unescape(x.nextTo(';')).trim());
          // discard the ';'
          x.next();
          // parse the remaining cookie attributes
          while (x.more()) {
              name = unescape(x.nextTo("=;")).trim().toLowerCase(Locale.ROOT);
              // don't allow a cookies attributes to overwrite its 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() != '=') {
                  value = Boolean.TRUE;
              } else {
                  value = unescape(x.nextTo(';')).trim();
                  x.next();
              }
              // only store non-blank attributes
              if(!"".equals(name) && !"".equals(value)) {
                  jo.put(name, value);
              }
          }
          return jo;
      }
  
  
      /**
       * Convert a JSONObject into a cookie specification string. The JSONObject
       * must contain "name" and "value" members (case insensitive).
       * 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: <a href="https://tools.ietf.org/html/rfc6265">https://tools.ietf.org/html/rfc6265</a>
       * @param jo A JSONObject
       * @return A cookie specification string
       * @throws JSONException thrown if the cookie has no name.
       */
      public static String toString(JSONObject jo) throws JSONException {
          StringBuilder sb = new StringBuilder();
          
          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(escape((String)value));
          
          for(String key : jo.keySet()){
              if("name".equalsIgnoreCase(key)
                      || "value".equalsIgnoreCase(key)) {
                  // already processed above
                  continue;
              }
              value = jo.opt(key);
              if(value instanceof Boolean) {
                  if(Boolean.TRUE.equals(value)) {
                      sb.append(';').append(escape(key));
                  }
                  // don't emit false values
              } else {
                  sb.append(';')
                      .append(escape(key))
                      .append('=')
                      .append(escape(value.toString()));
              }
          }
          
          return sb.toString();
      }
  
      /**
       * Convert <code>%</code><i>hh</i> sequences to single characters, and
       * convert plus to space.
       * @param string A string that may contain
       *      <code>+</code>&nbsp;<small>(plus)</small> and
       *      <code>%</code><i>hh</i> sequences.
       * @return The unescaped string.
       */
      public static String unescape(String string) {
          int length = string.length();
          StringBuilder sb = new StringBuilder(length);
          for (int i = 0; i < length; ++i) {
              char c = string.charAt(i);
              if (c == '+') {
                  c = ' ';
              } else if (c == '%' && i + 2 < length) {
                  int d = JSONTokener.dehexchar(string.charAt(i + 1));
                  int e = JSONTokener.dehexchar(string.charAt(i + 2));
                  if (d >= 0 && e >= 0) {
                      c = (char)(d * 16 + e);
                      i += 2;
                  }
              }
              sb.append(c);
          }
          return sb.toString();
      }
  }
  