From fc15f38fa8b3cc4d7ac14024d7f0bd5f6db6539e Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Thu, 14 Aug 2025 12:56:32 -0700 Subject: [PATCH 1/2] Issue 53699: Use getInputStream() instead of getReader() to avoid BufferUnderflow --- .../org/labkey/api/action/BaseApiAction.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/api/src/org/labkey/api/action/BaseApiAction.java b/api/src/org/labkey/api/action/BaseApiAction.java index 821fcc25316..ff0ebe377b8 100644 --- a/api/src/org/labkey/api/action/BaseApiAction.java +++ b/api/src/org/labkey/api/action/BaseApiAction.java @@ -39,6 +39,7 @@ import org.labkey.api.util.MimeMap; import org.labkey.api.util.Pair; import org.labkey.api.util.ResponseHelper; +import org.labkey.api.util.StringUtilsLabKey; import org.labkey.api.view.BadRequestException; import org.labkey.api.view.NotFoundException; import org.labkey.api.view.UnauthorizedException; @@ -52,6 +53,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import java.io.InputStreamReader; import java.io.Reader; import java.net.SocketTimeoutException; import java.time.Duration; @@ -511,12 +513,23 @@ protected long getMaximumJsonInputLength() private @Nullable JSONObject getJsonObject() throws IOException { + HttpServletRequest request = getViewContext().getRequest(); + if (request == null) + return null; + + String characterEncoding = request.getCharacterEncoding(); + if (characterEncoding == null) + characterEncoding = StringUtilsLabKey.DEFAULT_CHARSET.name(); + long maxLength = getMaximumJsonInputLength(); - try (Reader r = getViewContext().getRequest().getReader(); - Reader jsonReader = maxLength > 0 ? new StrictBoundedReader(r, maxLength) : r) + + // Issue 53699: Use request.getInputStream() instead of request.getReader() to + // avoid BufferUnderflowException when processing multibyte character JSON payloads. + try (Reader streamReader = new InputStreamReader(request.getInputStream(), characterEncoding); + Reader jsonReader = maxLength > 0 ? new StrictBoundedReader(streamReader, maxLength) : streamReader) { - JSONTokener tokener = new JSONTokener(r); - return tokener.more() ? new JSONObject(new JSONTokener(jsonReader)) : null; + JSONTokener tokener = new JSONTokener(jsonReader); + return tokener.more() ? new JSONObject(tokener) : null; } } From 10226106d8b048b3ba74614a8824af85d1edf963 Mon Sep 17 00:00:00 2001 From: labkey-nicka Date: Thu, 14 Aug 2025 12:56:58 -0700 Subject: [PATCH 2/2] Update LengthValidator to check for character code point length --- api/src/org/labkey/api/data/validator/LengthValidator.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/src/org/labkey/api/data/validator/LengthValidator.java b/api/src/org/labkey/api/data/validator/LengthValidator.java index 2aace6b1984..0b93cdf96de 100644 --- a/api/src/org/labkey/api/data/validator/LengthValidator.java +++ b/api/src/org/labkey/api/data/validator/LengthValidator.java @@ -35,8 +35,9 @@ public String _validate(int rowNum, Object value) { if (value instanceof String s) { - if (s.length() > scale) - return "Value is too long for column '" + _columnName + "', a maximum length of " + scale + " is allowed. The supplied value, '" + StringUtils.abbreviateMiddle(s, "...", 50) + "', was " + s.length() + " characters long."; + int charLength = Character.codePointCount(s, 0, s.length()); + if (charLength > scale) + return "Value is too long for column '" + _columnName + "', a maximum length of " + scale + " is allowed. The supplied value, '" + StringUtils.abbreviateMiddle(s, "...", 50) + "', was " + charLength + " characters long."; } return null;