@@ -55,6 +55,8 @@ const CHAR_QUESTION = 0x3f;
5555const CHAR_AT = 0x40 ;
5656const CHAR_UNDERSCORE = 0x5f ;
5757const CHAR_TILDE = 0x7e ;
58+ const CHAR_EQUALS = 0x3d ;
59+ const CHAR_AMP = 0x26 ;
5860
5961const CHAR_0 = 0x30 ;
6062const CHAR_E = 0x45 ;
@@ -77,6 +79,7 @@ const STATE_IN_OBJECT = 6;
7779
7880const ERR_MSG_EXPECT_STRUCTCHAR =
7981 "JSON->URL: expected comma, open paren, or close paren" ;
82+ const ERR_MSG_EXPECT_MOREARRAY = "JSON->URL: expected comma or close paren" ;
8083const ERR_MSG_EXPECT_VALUE = "JSON->URL: expected value" ;
8184const ERR_MSG_EXPECT_LITERAL = "JSON->URL: expected literal value" ;
8285const ERR_MSG_EXPECT_OBJVALUE = "JSON->URL: expected object value" ;
@@ -187,18 +190,18 @@ function errorMessage(msg, pos) {
187190 return msg + " at position " + pos ;
188191}
189192
190- function parseLiteralLength ( text , i , len , errmsg ) {
193+ function parseLiteralLength ( text , i , end , errmsg ) {
191194 var isQuote = false ;
192195 var start = i ;
193196
194- if ( i === len ) {
197+ if ( i === end ) {
195198 throw new SyntaxError ( errorMessage ( errmsg , i ) ) ;
196199 }
197200 if ( text . charCodeAt ( i ) === CHAR_QUOTE ) {
198201 isQuote = true ;
199202 i ++ ;
200203 }
201- for ( ; i < len ; i ++ ) {
204+ for ( ; i < end ; i ++ ) {
202205 var c = text . charCodeAt ( i ) ;
203206 if ( c >= 0x41 && c <= 0x5a ) {
204207 // A-Z
@@ -243,12 +246,21 @@ function parseLiteralLength(text, i, len, errmsg) {
243246 return i ;
244247 }
245248 continue ;
249+ case CHAR_AMP :
250+ case CHAR_EQUALS :
251+ //
252+ // these are forbidden, quoted or otherwise.
253+ //
254+ if ( i === start ) {
255+ throw new SyntaxError ( errorMessage ( errmsg , start ) ) ;
256+ }
257+ return i ;
246258 default :
247259 throw new SyntaxError ( errorMessage ( ERR_MSG_BADCHAR , i ) ) ;
248260 }
249261 }
250262
251- return len ;
263+ return end ;
252264}
253265
254266function toJsonURLText_Boolean ( ) {
@@ -500,6 +512,13 @@ class ValueStack extends Array {
500512class JsonURL {
501513 /**
502514 * Construct a new JsonURL class.
515+ *
516+ * Each instance of this class contains a number of properties that manage
517+ * the behavior of the parser and the values it returns; these are documented
518+ * below. The class instance does not manage parse state -- that state is
519+ * local to the parse() function itself. As long as you don't need different
520+ * properties (e.g. limits, null value, etc) you may re-use the same Parser
521+ * instance, even by multiple Workers.
503522 * @param {Object } prop Initialization properties.
504523 * You may provide zero more more of the following. Reasonable defaults
505524 * are assumed.
@@ -643,6 +662,27 @@ class JsonURL {
643662 /**
644663 * Parse JSON->URL text.
645664 * @param {string } text The text to parse.
665+ * @param {Object } options parse options.
666+ * You may provide zero more more of the following.
667+ * @param {array } options.impliedArray An implied array.
668+ * The parse() method implements a parser for the grammar oulined in
669+ * section 2.7 of the JSON->URL specification. The given parse text
670+ * is assumed to be an array, and the leading and trailing parens must
671+ * not be present. The given prop.impliedArray value will be populated
672+ * and returned.
673+ * @param {object } options.impliedObject An implied object.
674+ * The parse() method implements a parser for the grammar oulined in
675+ * section 2.8 of the JSON->URL specification. The given parse text
676+ * is assumed to be an object, and the leading and trailing parens must
677+ * not be present. The given prop.impliedObject value will be populated
678+ * and returned.
679+ * @param {boolean } options.wwwFormUrlEncoded Enable support for
680+ * x-www-form-urlencoded content.
681+ * The parse() method implements a parser for the grammar oulined in
682+ * section 2.9 of the JSON->URL specification. The given parse text
683+ * is may use ampersand and equal characters as the value and member
684+ * separator characters, respetively, at the top-level. This may be
685+ * combined with prop.impliedArray or prop.impliedObject.
646686 * @throws SyntaxError if there is a syntax error in the given text
647687 * @throws Error if a limit given in the constructor (or its default)
648688 * is exceeded.
@@ -664,10 +704,10 @@ class JsonURL {
664704 let stateStack = new StateStack ( this ) ;
665705 let pos = 0 ;
666706
667- if ( options . impliedObject ) {
707+ if ( options . impliedObject !== undefined ) {
668708 valueStack . push ( options . impliedObject ) ;
669709 stateStack . push ( STATE_IN_OBJECT ) ;
670- } else if ( options . impliedArray ) {
710+ } else if ( options . impliedArray !== undefined ) {
671711 valueStack . push ( options . impliedArray ) ;
672712 stateStack . push ( STATE_IN_ARRAY ) ;
673713 } else if ( text . charCodeAt ( 0 ) !== CHAR_PAREN_OPEN ) {
@@ -725,18 +765,26 @@ class JsonURL {
725765 continue ;
726766
727767 case CHAR_PAREN_CLOSE :
768+ pos ++ ;
769+
728770 if ( stateStack . depth ( true ) === - 1 ) {
729- if ( pos + 1 != end ) {
730- throw new SyntaxError ( errorMessage ( ERR_MSG_EXTRACHARS , pos ) ) ;
731- }
732- if ( valueStack . length === 0 ) {
771+ if ( pos === end ) {
733772 return newEmptyValue ( this ) ;
734773 }
735- return valueStack [ 0 ] ;
774+ throw new SyntaxError ( errorMessage ( ERR_MSG_EXTRACHARS , pos ) ) ;
736775 }
737776
738777 valueStack . appendArrayValue ( pos , newEmptyValue ( this ) ) ;
739- pos ++ ;
778+
779+ if ( pos === end && stateStack . depth ( ) === 0 ) {
780+ if ( options . impliedArray ) {
781+ return valueStack . popArrayValue ( ) ;
782+ }
783+ if ( options . impliedObject ) {
784+ return valueStack . popObjectValue ( ) ;
785+ }
786+ throw new SyntaxError ( errorMessage ( ERR_MSG_STILLOPEN , pos ) ) ;
787+ }
740788 continue ;
741789
742790 default :
@@ -762,6 +810,12 @@ class JsonURL {
762810 pos = lvpos ;
763811
764812 switch ( c ) {
813+ case CHAR_AMP :
814+ if ( ! options . wwwFormUrlEncoded || stateStack . depth ( ) > 0 ) {
815+ throw new SyntaxError ( errorMessage ( ERR_MSG_BADCHAR , pos ) ) ;
816+ }
817+ // fall through
818+
765819 case CHAR_COMMA :
766820 //
767821 // multi-element array
@@ -772,21 +826,44 @@ class JsonURL {
772826 continue ;
773827
774828 case CHAR_PAREN_CLOSE :
829+ pos ++ ;
830+
775831 //
776832 // single element array
777833 //
778834 valueStack . appendArrayValue ( pos , [ lv ] ) ;
779835
780- if ( stateStack . depth ( true ) === - 1 ) {
781- if ( pos + 1 === end ) {
782- return valueStack [ 0 ] ;
783- }
784- throw new SyntaxError ( errorMessage ( ERR_MSG_EXTRACHARS , pos ) ) ;
836+ switch ( stateStack . depth ( true ) ) {
837+ case - 1 :
838+ if ( pos === end ) {
839+ return valueStack [ 0 ] ;
840+ }
841+ throw new SyntaxError ( errorMessage ( ERR_MSG_EXTRACHARS , pos ) ) ;
842+
843+ case 0 :
844+ if ( pos === end ) {
845+ if ( options . impliedArray ) {
846+ return valueStack . popArrayValue ( ) ;
847+ }
848+ if ( options . impliedObject ) {
849+ return valueStack . popObjectValue ( ) ;
850+ }
851+ throw new SyntaxError ( errorMessage ( ERR_MSG_STILLOPEN , pos ) ) ;
852+ }
853+ break ;
854+
855+ default :
856+ break ;
785857 }
786858
787- pos ++ ;
788859 continue ;
789860
861+ case CHAR_EQUALS :
862+ if ( ! options . wwwFormUrlEncoded || stateStack . depth ( ) > 0 ) {
863+ throw new SyntaxError ( errorMessage ( ERR_MSG_BADCHAR , pos ) ) ;
864+ }
865+ // fall through
866+
790867 case CHAR_COLON :
791868 //
792869 // key name for object
@@ -817,7 +894,7 @@ class JsonURL {
817894 lv = this . parseLiteral ( text , pos , lvpos , false ) ;
818895 pos = lvpos ;
819896
820- if ( lvpos === end ) {
897+ if ( pos === end ) {
821898 if ( stateStack . depth ( ) === 0 && options . impliedArray ) {
822899 return valueStack . popArrayValue ( lv ) ;
823900 }
@@ -832,18 +909,26 @@ class JsonURL {
832909 valueStack . popArrayValue ( ) ;
833910
834911 switch ( c ) {
912+ case CHAR_AMP :
913+ if ( ! options . wwwFormUrlEncoded || stateStack . depth ( ) > 0 ) {
914+ throw new SyntaxError ( errorMessage ( ERR_MSG_BADCHAR , pos ) ) ;
915+ }
916+ // fall through
917+
835918 case CHAR_COMMA :
836919 stateStack . replace ( STATE_IN_ARRAY ) ;
837920 pos ++ ;
838921 continue ;
839922
840923 case CHAR_PAREN_CLOSE :
924+ pos ++ ;
925+
841926 switch ( stateStack . depth ( true ) ) {
842927 case - 1 :
843928 //
844929 // end of a "real" composite
845930 //
846- if ( pos + 1 == end ) {
931+ if ( pos === end && ! options . impliedArray ) {
847932 return valueStack [ 0 ] ;
848933 }
849934 throw new SyntaxError ( errorMessage ( ERR_MSG_EXTRACHARS , pos ) ) ;
@@ -852,21 +937,21 @@ class JsonURL {
852937 //
853938 // end of an implied composite
854939 //
855- if ( pos + 1 == end ) {
940+ if ( pos = == end ) {
856941 if ( options . impliedArray ) {
857942 return valueStack . popArrayValue ( ) ;
858943 }
859944 if ( options . impliedObject ) {
860945 return valueStack . popObjectValue ( ) ;
861946 }
947+ throw new SyntaxError ( errorMessage ( ERR_MSG_STILLOPEN , pos ) ) ;
862948 }
863949 break ;
864950 }
865951
866- pos ++ ;
867952 continue ;
868953 }
869- throw new SyntaxError ( errorMessage ( ERR_MSG_EXPECT_STRUCTCHAR , pos ) ) ;
954+ throw new SyntaxError ( errorMessage ( ERR_MSG_EXPECT_MOREARRAY , pos ) ) ;
870955
871956 case STATE_OBJECT_HAVE_KEY :
872957 if ( c === CHAR_PAREN_OPEN ) {
@@ -900,50 +985,79 @@ class JsonURL {
900985 valueStack . popObjectValue ( ) ;
901986
902987 switch ( c ) {
988+ case CHAR_AMP :
989+ if ( ! options . wwwFormUrlEncoded || stateStack . depth ( ) > 0 ) {
990+ throw new SyntaxError ( errorMessage ( ERR_MSG_BADCHAR , pos ) ) ;
991+ }
992+ // fall through
993+
903994 case CHAR_COMMA :
904995 stateStack . replace ( STATE_IN_OBJECT ) ;
905996 pos ++ ;
906997 continue ;
907998
908999 case CHAR_PAREN_CLOSE :
1000+ pos ++ ;
1001+
9091002 switch ( stateStack . depth ( true ) ) {
9101003 case - 1 :
911- if ( pos + 1 === end ) {
1004+ if ( pos === end && ! options . impliedObject ) {
9121005 //
9131006 // end of a "real" object
9141007 //
9151008 return valueStack [ 0 ] ;
9161009 }
9171010 throw new SyntaxError ( errorMessage ( ERR_MSG_EXTRACHARS , pos ) ) ;
1011+
9181012 case 0 :
9191013 //
9201014 // end of an implied composite
9211015 //
922- if ( pos + 1 == end ) {
1016+ if ( pos = == end ) {
9231017 if ( options . impliedArray ) {
9241018 return valueStack . popArrayValue ( ) ;
9251019 }
9261020 if ( options . impliedObject ) {
9271021 return valueStack . popObjectValue ( ) ;
9281022 }
1023+ throw new SyntaxError (
1024+ errorMessage ( ERR_MSG_EXTRACHARS , pos )
1025+ ) ;
9291026 }
9301027 break ;
1028+
1029+ default :
1030+ break ;
9311031 }
9321032
933- pos ++ ;
9341033 continue ;
9351034 }
9361035 throw new SyntaxError ( errorMessage ( ERR_MSG_EXPECT_STRUCTCHAR , pos ) ) ;
9371036
9381037 case STATE_IN_OBJECT :
9391038 lvpos = parseLiteralLength ( text , pos , end , ERR_MSG_EXPECT_LITERAL ) ;
9401039 if ( lvpos === end ) {
1040+ //
1041+ // I don't know that this is actually possible -- I haven't
1042+ // found a test case yet. But, if it is possible, it's an error.
1043+ //
9411044 throw new SyntaxError ( errorMessage ( ERR_MSG_STILLOPEN , end ) ) ;
9421045 }
9431046
9441047 c = text . charCodeAt ( lvpos ) ;
945- if ( c !== CHAR_COLON ) {
946- throw new SyntaxError ( ( ERR_MSG_EXPECT_OBJVALUE , lvpos ) ) ;
1048+
1049+ switch ( c ) {
1050+ case CHAR_EQUALS :
1051+ if ( ! options . wwwFormUrlEncoded || stateStack . depth ( ) > 0 ) {
1052+ throw new SyntaxError ( errorMessage ( ERR_MSG_BADCHAR , pos ) ) ;
1053+ }
1054+ // fall through
1055+
1056+ case CHAR_COLON :
1057+ break ;
1058+
1059+ default :
1060+ throw new SyntaxError ( ( ERR_MSG_EXPECT_OBJVALUE , lvpos ) ) ;
9471061 }
9481062
9491063 lv = this . parseLiteral ( text , pos , lvpos , true ) ;
@@ -954,6 +1068,9 @@ class JsonURL {
9541068 continue ;
9551069
9561070 default :
1071+ //
1072+ // this shouldn't be possible, but handle it just in case
1073+ //
9571074 throw new SyntaxError ( errorMessage ( ERR_MSG_INTERNAL , pos ) ) ;
9581075 }
9591076 }
0 commit comments