@@ -53,11 +53,23 @@ class JsonUrlGrammarAQF extends AbstractGrammar {
5353 private static final char ESCAPE = '!' ;
5454
5555 /**
56- * Buffer for decoded literal text.
56+ * Buffer for decoded/unescaped literal text.
5757 */
5858 @ SuppressWarnings ("PMD.AvoidStringBufferField" ) // reused
5959 private final StringBuilder decodedTextBuffer = new StringBuilder (512 );
6060
61+ /**
62+ * Buffer for non-decoded/escaped literal text.
63+ */
64+ @ SuppressWarnings ("PMD.AvoidStringBufferField" ) // reused
65+ private final StringBuilder numTextBuffer = new StringBuilder (512 );
66+
67+ /**
68+ * Reference to either {@link #decodedTextBuffer} or
69+ * {@link #numTextBuffer}.
70+ */
71+ private CharSequence literalText ;
72+
6173 /**
6274 * Construct a new JsonUrlGrammar.
6375 * @param text input text
@@ -77,7 +89,7 @@ public JsonUrlGrammarAQF(
7789 */
7890 @ SuppressWarnings ("PMD.CyclomaticComplexity" )
7991 private void decodeBang (StringBuilder decodedText , boolean isFirst ) {
80- int cur = nextCodePoint ();
92+ int cur = nextCodePoint (false );
8193
8294 switch (cur ) {
8395 case '0' :
@@ -91,6 +103,7 @@ private void decodeBang(StringBuilder decodedText, boolean isFirst) {
91103 case '8' :
92104 case '9' :
93105 case '-' :
106+ case '+' :
94107 case ESCAPE :
95108 case 't' :
96109 case 'f' :
@@ -103,7 +116,7 @@ private void decodeBang(StringBuilder decodedText, boolean isFirst) {
103116 break ;
104117
105118 case 'e' :
106- if (isFirst && decodedText . length () == 0 ) {
119+ if (isFirst ) {
107120 break ;
108121 }
109122 // fall through
@@ -118,45 +131,62 @@ protected boolean readAndBufferLiteral() {
118131 final StringBuilder decodedText = this .decodedTextBuffer ;
119132 decodedText .setLength (0 );
120133
134+ final StringBuilder numText = this .numTextBuffer ;
135+ numText .setLength (0 );
136+
121137 //
122138 // return true if this has an escape sequence and therefore
123139 // must be parsed as a string value; otherwise, return false.
124140 //
125141 boolean ret = false ;
126142
127- for (;;) {
143+ for (boolean isFirst = true ;; isFirst = false ) {
144+ final char rawPlus ;
145+
128146 //
129147 // The browser address bar *does* recognize a difference between
130- // percent encoded vs. literal ampersand and equals , unlike other
131- // sub-delims. So I have to check for those specifically, here ,
132- // because the call to nextCodePoint() will decode them and I can't
133- // tell the difference at that point.
148+ // percent encoded vs. literal ampersand, equals, and plus , unlike
149+ // other sub-delims. So I have to check for those specifically,
150+ // here, because the call to nextCodePoint() will decode them and
151+ // I can't tell the difference at that point.
134152 //
135153 switch (peekAscii ()) { // NOPMD - no default
136154 case EOF :
137155 case WFU_VALUE_SEPARATOR :
138156 case WFU_NAME_SEPARATOR :
139157 return ret ;
158+ case WFU_SPACE :
159+ rawPlus = '+' ;
160+ break ;
161+ default :
162+ rawPlus = ' ' ;
163+ break ;
140164 }
141165
142- int ucp = nextCodePoint ();
166+ final int ucp = nextCodePoint ();
143167
144168 if (ucp >= CHARBITS_LENGTH ) {
145169 decodedText .appendCodePoint (ucp );
170+ numText .appendCodePoint (ucp );
146171 continue ;
147172 }
148173
149174 switch (CHARBITS [ucp ] & (IS_SPACE
150175 | IS_BANG | IS_LITCHAR | IS_STRUCTCHAR | IS_CGICHAR )) {
151176
152177 case IS_BANG | IS_LITCHAR :
153- decodeBang (decodedText , !ret );
178+ decodeBang (decodedText , isFirst );
179+ numText .appendCodePoint (ucp );
154180 ret = true ;
155181 continue ;
156- case IS_LITCHAR :
157182 case IS_SPACE :
183+ decodedText .appendCodePoint (ucp );
184+ numText .append (rawPlus );
185+ break ;
186+ case IS_LITCHAR :
158187 case IS_STRUCTCHAR | IS_CGICHAR :
159188 decodedText .appendCodePoint (ucp );
189+ numText .appendCodePoint (ucp );
160190 continue ;
161191 case IS_STRUCTCHAR :
162192 text .pushbackChar (ucp );
@@ -170,9 +200,12 @@ protected boolean readAndBufferLiteral() {
170200
171201 @ Override
172202 protected JsonUrlEvent readBufferedLiteral (
173- boolean wasEscapedString ,
203+ boolean isEscaped ,
174204 boolean isKey ) {
175205
206+ final StringBuilder decodedText = this .decodedTextBuffer ;
207+ literalText = decodedText ;
208+
176209 if (optionImpliedStringLiterals (options ())) {
177210 //
178211 // VALUE_STRING (rather than VALUE_EMPTY_LITERAL) should be
@@ -181,9 +214,7 @@ protected JsonUrlEvent readBufferedLiteral(
181214 return keyEvent (isKey , JsonUrlEvent .VALUE_STRING );
182215 }
183216
184- final StringBuilder decodedText = this .decodedTextBuffer ;
185-
186- if (wasEscapedString ) {
217+ if (isEscaped ) {
187218 if (decodedText .length () == 0 ) {
188219 return keyEvent (isKey , JsonUrlEvent .VALUE_EMPTY_LITERAL );
189220 }
@@ -208,7 +239,10 @@ && optionCoerceNullToEmptyString(options())) {
208239 return keyEvent (isKey , ret );
209240 }
210241
211- if (numberBuilder .reset ().parse (decodedText )) {
242+ final StringBuilder numText = this .numTextBuffer ;
243+
244+ if (numberBuilder .reset ().parse (numText , options ())) {
245+ literalText = numText ;
212246 return keyEvent (isKey , JsonUrlEvent .VALUE_NUMBER );
213247 }
214248
@@ -222,7 +256,7 @@ protected JsonUrlEvent readLiteral(boolean isKey) {
222256
223257 @ Override
224258 public String getString () {
225- return decodedTextBuffer .toString ();
259+ return literalText .toString ();
226260 }
227261
228262 @ Override
@@ -236,13 +270,20 @@ protected int peekChar() {
236270 text .pushbackChar (codePoint );
237271 return codePoint ;
238272 }
239-
273+
240274 /**
241275 * Read and decode the next codepoint.
242276 */
243277 private int nextCodePoint () {
278+ return nextCodePoint (true );
279+ }
280+
281+ /**
282+ * Read and decode the next codepoint.
283+ */
284+ private int nextCodePoint (boolean decodePlus ) {
244285 try {
245- return PercentCodec .decode (text );
286+ return PercentCodec .decode (text , decodePlus );
246287
247288 } catch (IOException e ) {
248289 SyntaxException tex = newSyntaxException (MSG_BAD_CHAR );
0 commit comments