@@ -284,62 +284,73 @@ public char nextClean() throws JSONException {
284284 * Backslash processing is done. The formal JSON format does not
285285 * allow strings in single quotes, but an implementation is allowed to
286286 * accept them.
287+ * If strictMode is true, this implementation will not accept unbalanced quotes (e.g will not accept <code>"test'</code>)
287288 * @param quote The quoting character, either
288289 * <code>"</code> <small>(double quote)</small> or
289290 * <code>'</code> <small>(single quote)</small>.
290- * @return A String.
291- * @throws JSONException Unterminated string.
291+ * @return A String.
292+ * @throws JSONException Unterminated string or unbalanced quotes if strictMode == true .
292293 */
293- public String nextString (char quote ) throws JSONException {
294+ public String nextString (char quote , boolean strictMode ) throws JSONException {
294295 char c ;
295296 StringBuilder sb = new StringBuilder ();
296297 for (;;) {
297298 c = this .next ();
298299 switch (c ) {
299- case 0 :
300- case '\n' :
301- case '\r' :
302- throw this .syntaxError ("Unterminated string" );
303- case '\\' :
304- c = this .next ();
305- switch (c ) {
306- case 'b' :
307- sb .append ('\b' );
308- break ;
309- case 't' :
310- sb .append ('\t' );
311- break ;
312- case 'n' :
313- sb .append ('\n' );
314- break ;
315- case 'f' :
316- sb .append ('\f' );
317- break ;
318- case 'r' :
319- sb .append ('\r' );
320- break ;
321- case 'u' :
322- try {
323- sb .append ((char )Integer .parseInt (this .next (4 ), 16 ));
324- } catch (NumberFormatException e ) {
325- throw this .syntaxError ("Illegal escape." , e );
326- }
327- break ;
328- case '"' :
329- case '\'' :
300+ case 0 :
301+ case '\n' :
302+ case '\r' :
303+ throw this .syntaxError ("Unterminated string" );
330304 case '\\' :
331- case '/' :
332- sb .append (c );
305+ c = this .next ();
306+ switch (c ) {
307+ case 'b' :
308+ sb .append ('\b' );
309+ break ;
310+ case 't' :
311+ sb .append ('\t' );
312+ break ;
313+ case 'n' :
314+ sb .append ('\n' );
315+ break ;
316+ case 'f' :
317+ sb .append ('\f' );
318+ break ;
319+ case 'r' :
320+ sb .append ('\r' );
321+ break ;
322+ case 'u' :
323+ try {
324+ sb .append ((char ) Integer .parseInt (this .next (4 ), 16 ));
325+ } catch (NumberFormatException e ) {
326+ throw this .syntaxError ("Illegal escape." , e );
327+ }
328+ break ;
329+ case '"' :
330+ case '\'' :
331+ case '\\' :
332+ case '/' :
333+ sb .append (c );
334+ break ;
335+ default :
336+ throw this .syntaxError ("Illegal escape." );
337+ }
333338 break ;
334339 default :
335- throw this .syntaxError ("Illegal escape." );
336- }
337- break ;
338- default :
339- if (c == quote ) {
340- return sb .toString ();
341- }
342- sb .append (c );
340+ if (strictMode && c == '\"' && quote != c ) {
341+ throw this .syntaxError (String .format (
342+ "Field contains unbalanced quotes. Starts with %s but ends with double quote." , quote ));
343+ }
344+
345+ if (strictMode && c == '\'' && quote != c ) {
346+ throw this .syntaxError (String .format (
347+ "Field contains unbalanced quotes. Starts with %s but ends with single quote." , quote ));
348+ }
349+
350+ if (c == quote ) {
351+ return sb .toString ();
352+ }
353+ sb .append (c );
343354 }
344355 }
345356 }
@@ -423,50 +434,10 @@ public Object nextValue(boolean strictMode) throws JSONException {
423434 this .back ();
424435 return getJsonArray ();
425436 default :
426- return getValue (c , strictMode );
437+ return nextSimpleValue (c , strictMode );
427438 }
428439 }
429440
430- /**
431- * This method is used to get the next value.
432- *
433- * @param c The next character in the JSONTokener.
434- * @param strictMode If true, the method will strictly adhere to the JSON syntax, throwing a JSONException if the
435- * value is not surrounded by quotes.
436- * @return An object which is the next value in the JSONTokener.
437- * @throws JSONException If the value is not surrounded by quotes when strictMode is true.
438- */
439- private Object getValue (char c , boolean strictMode ) {
440- if (strictMode ) {
441- Object valueToValidate = nextSimpleValue (c , true );
442-
443- boolean isNumeric = checkIfValueIsNumeric (valueToValidate );
444-
445- if (isNumeric ) {
446- return valueToValidate ;
447- }
448-
449- boolean hasQuotes = valueIsWrappedByQuotes (valueToValidate );
450-
451- if (!hasQuotes ) {
452- throw new JSONException ("Value is not surrounded by quotes: " + valueToValidate );
453- }
454-
455- return valueToValidate ;
456- }
457-
458- return nextSimpleValue (c );
459- }
460-
461- private boolean checkIfValueIsNumeric (Object valueToValidate ) {
462- for (char c : valueToValidate .toString ().toCharArray ()) {
463- if (!Character .isDigit (c )) {
464- return false ;
465- }
466- }
467- return true ;
468- }
469-
470441 /**
471442 * This method is used to get a JSONObject from the JSONTokener. The strictMode parameter controls the behavior of
472443 * the method when parsing the JSONObject.
@@ -502,38 +473,12 @@ private JSONArray getJsonArray() {
502473 }
503474 }
504475
505- /**
506- * This method checks if the provided value is wrapped by quotes.
507- *
508- * @param valueToValidate The value to be checked. It is converted to a string before checking.
509- * @return A boolean indicating whether the value is wrapped by quotes. It returns true if the value is wrapped by
510- * either single or double quotes.
511- */
512- private boolean valueIsWrappedByQuotes (Object valueToValidate ) {
513- String stringToValidate = valueToValidate .toString ();
514- boolean isWrappedByDoubleQuotes = isWrappedByQuotes (stringToValidate , "\" " );
515- boolean isWrappedBySingleQuotes = isWrappedByQuotes (stringToValidate , "'" );
516- return isWrappedByDoubleQuotes || isWrappedBySingleQuotes ;
517- }
518-
519- private boolean isWrappedByQuotes (String valueToValidate , String quoteType ) {
520- return valueToValidate .startsWith (quoteType ) && valueToValidate .endsWith (quoteType );
521- }
522-
523- Object nextSimpleValue (char c ) {
524- return nextSimpleValue (c , false );
525- }
526-
527476 Object nextSimpleValue (char c , boolean strictMode ) {
528477 if (c == '"' || c == '\'' ) {
529- String str = this .nextString (c );
530- if (strictMode ) {
531- return String .format ("\" %s\" " , str );
532- }
533- return str ;
478+ return this .nextString (c , strictMode );
534479 }
535480
536- return parsedUnquotedText (c );
481+ return parsedUnquotedText (c , strictMode );
537482 }
538483
539484 /**
@@ -545,7 +490,7 @@ Object nextSimpleValue(char c, boolean strictMode) {
545490 * @return The parsed object.
546491 * @throws JSONException If the parsed string is empty.
547492 */
548- private Object parsedUnquotedText (char c ) {
493+ private Object parsedUnquotedText (char c , boolean strictMode ) {
549494 StringBuilder sb = new StringBuilder ();
550495 while (c >= ' ' && ",:]}/\\ \" [{;=#" .indexOf (c ) < 0 ) {
551496 sb .append (c );
@@ -556,12 +501,36 @@ private Object parsedUnquotedText(char c) {
556501 }
557502
558503 String string = sb .toString ().trim ();
504+
505+ if (strictMode ) {
506+ boolean isBooleanOrNumeric = checkIfValueIsBooleanOrNumeric (string );
507+
508+ if (isBooleanOrNumeric ) {
509+ return string ;
510+ }
511+
512+ throw new JSONException (String .format ("Value is not surrounded by quotes: %s" , string ));
513+ }
514+
559515 if (string .isEmpty ()) {
560516 throw this .syntaxError ("Missing value" );
561517 }
562518 return JSONObject .stringToValue (string );
563519 }
564520
521+ private boolean checkIfValueIsBooleanOrNumeric (Object valueToValidate ) {
522+ String stringToValidate = valueToValidate .toString ();
523+ if (stringToValidate .equals ("true" ) || stringToValidate .equals ("false" )) {
524+ return true ;
525+ }
526+
527+ try {
528+ Double .parseDouble (stringToValidate );
529+ return true ;
530+ } catch (NumberFormatException e ) {
531+ return false ;
532+ }
533+ }
565534
566535 /**
567536 * Skip characters until the next character is the requested character.
0 commit comments