@@ -151,33 +151,108 @@ public Object nextEntity(@SuppressWarnings("unused") char ampersand) throws JSON
151151 /**
152152 * Unescape an XML entity encoding;
153153 * @param e entity (only the actual entity value, not the preceding & or ending ;
154- * @return
154+ * @return the unescaped entity string
155+ * @throws JSONException if the entity is malformed
155156 */
156- static String unescapeEntity (String e ) {
157+ static String unescapeEntity (String e ) throws JSONException {
157158 // validate
158159 if (e == null || e .isEmpty ()) {
159160 return "" ;
160161 }
161162 // if our entity is an encoded unicode point, parse it.
162163 if (e .charAt (0 ) == '#' ) {
163- int cp ;
164- if (e .charAt (1 ) == 'x' || e .charAt (1 ) == 'X' ) {
165- // hex encoded unicode
166- cp = Integer .parseInt (e .substring (2 ), 16 );
167- } else {
168- // decimal encoded unicode
169- cp = Integer .parseInt (e .substring (1 ));
164+ if (e .length () < 2 ) {
165+ throw new JSONException ("Invalid numeric character reference: &#;" );
170166 }
171- return new String (new int [] {cp },0 ,1 );
172- }
167+ int cp = (e .charAt (1 ) == 'x' || e .charAt (1 ) == 'X' )
168+ ? parseHexEntity (e )
169+ : parseDecimalEntity (e );
170+ return new String (new int [] {cp }, 0 , 1 );
171+ }
173172 Character knownEntity = entity .get (e );
174- if (knownEntity == null ) {
173+ if (knownEntity == null ) {
175174 // we don't know the entity so keep it encoded
176175 return '&' + e + ';' ;
177176 }
178177 return knownEntity .toString ();
179178 }
180179
180+ /**
181+ * Parse a hexadecimal numeric character reference (e.g., "઼").
182+ * @param e entity string starting with '#' (e.g., "#x1F4A9")
183+ * @return the Unicode code point
184+ * @throws JSONException if the format is invalid
185+ */
186+ private static int parseHexEntity (String e ) throws JSONException {
187+ // hex encoded unicode - need at least one hex digit after #x
188+ if (e .length () < 3 ) {
189+ throw new JSONException ("Invalid hex character reference: missing hex digits in &#" + e .substring (1 ) + ";" );
190+ }
191+ String hex = e .substring (2 );
192+ if (!isValidHex (hex )) {
193+ throw new JSONException ("Invalid hex character reference: &#" + e .substring (1 ) + ";" );
194+ }
195+ try {
196+ return Integer .parseInt (hex , 16 );
197+ } catch (NumberFormatException nfe ) {
198+ throw new JSONException ("Invalid hex character reference: &#" + e .substring (1 ) + ";" , nfe );
199+ }
200+ }
201+
202+ /**
203+ * Parse a decimal numeric character reference (e.g., "{").
204+ * @param e entity string starting with '#' (e.g., "#123")
205+ * @return the Unicode code point
206+ * @throws JSONException if the format is invalid
207+ */
208+ private static int parseDecimalEntity (String e ) throws JSONException {
209+ String decimal = e .substring (1 );
210+ if (!isValidDecimal (decimal )) {
211+ throw new JSONException ("Invalid decimal character reference: &#" + decimal + ";" );
212+ }
213+ try {
214+ return Integer .parseInt (decimal );
215+ } catch (NumberFormatException nfe ) {
216+ throw new JSONException ("Invalid decimal character reference: &#" + decimal + ";" , nfe );
217+ }
218+ }
219+
220+ /**
221+ * Check if a string contains only valid hexadecimal digits.
222+ * @param s the string to check
223+ * @return true if s is non-empty and contains only hex digits (0-9, a-f, A-F)
224+ */
225+ private static boolean isValidHex (String s ) {
226+ if (s == null || s .isEmpty ()) {
227+ return false ;
228+ }
229+ for (int i = 0 ; i < s .length (); i ++) {
230+ char c = s .charAt (i );
231+ if (!((c >= '0' && c <= '9' ) || (c >= 'a' && c <= 'f' ) || (c >= 'A' && c <= 'F' ))) {
232+ return false ;
233+ }
234+ }
235+ return true ;
236+ }
237+
238+ /**
239+ * Check if a string contains only valid decimal digits.
240+ * @param s the string to check
241+ * @return true if s is non-empty and contains only digits (0-9)
242+ */
243+ private static boolean isValidDecimal (String s ) {
244+ if (s == null || s .isEmpty ()) {
245+ return false ;
246+ }
247+ for (int i = 0 ; i < s .length (); i ++) {
248+ char c = s .charAt (i );
249+ if (c < '0' || c > '9' ) {
250+ return false ;
251+ }
252+ }
253+ return true ;
254+ }
255+
181256
182257 /**
183258 * <pre>{@code
0 commit comments