Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions etc/config/copyright-exclude
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ MANIFEST.MF
/etc/config/copyright-exclude
/etc/config/copyright.txt
/bundles/dist/src/main/resources/README.txt
/impl/src/test/java/org/eclipse/parsson/tests/JsonDocumentParseLimitTest.java

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that file has copyright, can we remove this line?

@mswatosh mswatosh May 22, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently the copyright checker is only set to accept the Oracle license. The tricky part with the Eclipse AI license is the last line is variable depending on which AI model was used. As far as I could tell the copyright checker plugin has no way to handle variable text other than the year.

Unless the copyright checker is updated to support this, it will probably be best to remove the plugin for the next release (aligning with EE12). That seemed beyond the scope of this PR though, so I just had it ignore this file for now.

18 changes: 17 additions & 1 deletion impl/src/main/java/org/eclipse/parsson/JsonContext.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2026 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -43,6 +43,9 @@ final class JsonContext {
/** Default maximum level of nesting. */
private static final int DEFAULT_MAX_DEPTH = 1000;

/** Default maximum number of characters to parse from one document. */
private static final int DEFAULT_MAX_PARSING_LIMIT = 15_000_000;

/**
* Custom char[] pool instance property. Can be set in properties {@code Map} only.
*/
Expand All @@ -59,6 +62,9 @@ final class JsonContext {
// Maximum depth to parse
private final int depthLimit;

// Maximum number of characters to parse from one document
private final int maxParsingLimit;

// Whether JSON pretty printing is enabled
private final boolean prettyPrinting;

Expand All @@ -77,6 +83,7 @@ final class JsonContext {
this.bigIntegerScaleLimit = getIntConfig(JsonConfig.MAX_BIGINTEGER_SCALE, config, DEFAULT_MAX_BIGINTEGER_SCALE);
this.bigDecimalLengthLimit = getIntConfig(JsonConfig.MAX_BIGDECIMAL_LEN, config, DEFAULT_MAX_BIGDECIMAL_LEN);
this.depthLimit = getIntConfig(JsonConfig.MAX_DEPTH, config, DEFAULT_MAX_DEPTH);
this.maxParsingLimit = getIntConfig(JsonConfig.MAX_PARSING_LIMIT, config, DEFAULT_MAX_PARSING_LIMIT);
this.prettyPrinting = getBooleanConfig(JsonGenerator.PRETTY_PRINTING, config);
this.rejectDuplicateKeys = getBooleanConfig(JsonConfig.REJECT_DUPLICATE_KEYS, config);
this.bufferPool = getBufferPool(config, defaultPool);
Expand All @@ -94,6 +101,7 @@ final class JsonContext {
this.bigIntegerScaleLimit = getIntConfig(JsonConfig.MAX_BIGINTEGER_SCALE, config, DEFAULT_MAX_BIGINTEGER_SCALE);
this.bigDecimalLengthLimit = getIntConfig(JsonConfig.MAX_BIGDECIMAL_LEN, config, DEFAULT_MAX_BIGDECIMAL_LEN);
this.depthLimit = getIntConfig(JsonConfig.MAX_DEPTH, config, DEFAULT_MAX_DEPTH);
this.maxParsingLimit = getIntConfig(JsonConfig.MAX_PARSING_LIMIT, config, DEFAULT_MAX_PARSING_LIMIT);
this.prettyPrinting = getBooleanConfig(JsonGenerator.PRETTY_PRINTING, config);
this.rejectDuplicateKeys = getBooleanConfig(JsonConfig.REJECT_DUPLICATE_KEYS, config);
this.bufferPool = getBufferPool(config, defaultPool);
Expand Down Expand Up @@ -121,6 +129,10 @@ int depthLimit() {
return depthLimit;
}

int maxParsingLimit() {
return maxParsingLimit;
}

boolean prettyPrinting() {
return prettyPrinting;
}
Expand Down Expand Up @@ -149,6 +161,7 @@ private static int getIntConfig(String propertyName, Map<String, ?> config, int
return intConfig != null ? intConfig : defaultValue;
}


private static boolean getBooleanConfig(String propertyName, Map<String, ?> config) throws JsonException {
// Try config Map first
Boolean booleanConfig = config != null ? getBooleanProperty(propertyName, config) : null;
Expand All @@ -175,6 +188,7 @@ private static Integer getIntProperty(String propertyName, Map<String, ?> config
propertyName, property.getClass().getName()));
}


// Returns true when property exists or null otherwise. Property value is ignored.
private static Boolean getBooleanProperty(String propertyName, Map<String, ?> config) throws JsonException {
return config.containsKey(propertyName) ? true : null;
Expand All @@ -189,6 +203,7 @@ private static Integer getIntSystemProperty(String propertyName) throws JsonExce
return propertyStringToInt(propertyName, systemProperty);
}


// Returns true when property exists or false otherwise. Property value is ignored.
private static boolean getBooleanSystemProperty(String propertyName) throws JsonException {
return getSystemProperty(propertyName) != null;
Expand All @@ -213,6 +228,7 @@ private static int propertyStringToInt(String propertyName, String propertyValue
}
}


// Constructor helper: Copy provider specific properties Map. Only specified properties are added.
// Instance prettyPrinting and rejectDuplicateKeys variables must be initialized before
// this method is called.
Expand Down
6 changes: 5 additions & 1 deletion impl/src/main/java/org/eclipse/parsson/JsonMessages.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2026 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -125,6 +125,10 @@ static String DUPLICATE_KEY(String name) {
return localize("parser.duplicate.key", name);
}

static String PARSER_COUNT_EXCEEDED(int limit) {
return localize("parser.count.exceeded", limit);
}

// generator messages
static String GENERATOR_FLUSH_IO_ERR() {
return localize("generator.flush.io.err");
Expand Down
18 changes: 17 additions & 1 deletion impl/src/main/java/org/eclipse/parsson/JsonTokenizer.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2026 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -85,6 +85,13 @@ final class JsonTokenizer implements Closeable {
private long bufferOffset = 0;
private boolean closed = false;

// Tracks the total number of parse operations (character reads) during JSON parsing.
// Note: This count represents parse operations by the parser's control flow,
// which is higher than the literal JSON source length due to lookahead
// operations needed to determine parsing completion (e.g., detecting EOF after the
// last token). See JsonConfig.MAX_PARSING_LIMIT for details.
private int documentParseCount = 0;

private boolean minus;
private boolean fracOrExp;
private BigDecimal bd;
Expand Down Expand Up @@ -136,6 +143,7 @@ private void readString() {
if (inPlace) {
int ch;
while(readBegin < readEnd && ((ch=buf[readBegin]) >= 0x20) && ch != '\\') {
incrementParseCount();
if (ch == '"') {
storeEnd = readBegin++; // ++ to consume quote char
return; // Got the entire string
Expand Down Expand Up @@ -214,6 +222,7 @@ private void unescape() {
// of resizing, filling up the buf, adjusting the pointers
private int readNumberChar() {
if (readBegin < readEnd) {
incrementParseCount();
return buf[readBegin++];
} else {
storeEnd = readBegin;
Expand Down Expand Up @@ -462,12 +471,19 @@ private int read() {
readBegin = storeEnd;
readEnd = readBegin+len;
}
incrementParseCount();
return buf[readBegin++];
} catch (IOException ioe) {
throw new JsonException(JsonMessages.TOKENIZER_IO_ERR(), ioe);
}
}

private void incrementParseCount() {
if (++documentParseCount > jsonContext.maxParsingLimit()) {
throw new JsonException(JsonMessages.PARSER_COUNT_EXCEEDED(jsonContext.maxParsingLimit()));
}
}

private int fillBuf() throws IOException {
if (storeEnd != 0) {
int storeLen = storeEnd-storeBegin;
Expand Down
18 changes: 17 additions & 1 deletion impl/src/main/java/org/eclipse/parsson/api/JsonConfig.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2026 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -40,6 +40,22 @@ public interface JsonConfig {
*/
String MAX_DEPTH = "org.eclipse.parsson.maxDepth";

/**
* Configuration property to limit the total number of characters consumed during parsing
* of a single JSON document.
* <p>
* This property limits all characters consumed by the tokenizer during parsing,
* including whitespace, structural characters, object member names, string values,
* number lexemes, and literal keywords.
* <p>
* <b>Important:</b> This limit represents the number of characters the parser must
* consume to complete parsing, which is higher than the literal JSON
* source length.
* <p>
* Default value is set to {@code 15000000} (15 million characters).
*/
String MAX_PARSING_LIMIT = "org.eclipse.parsson.maxParsingLimit";

/**
* Configuration property to reject duplicate keys.
* The value of the property could be anything.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2013, 2024 Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2013, 2026 Oracle and/or its affiliates. All rights reserved.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -46,6 +46,7 @@ parser.input.enc.detect.failed=Cannot auto-detect encoding, not enough chars
parser.input.enc.detect.ioerr=I/O error while auto-detecting the encoding of stream
parser.duplicate.key=Duplicate key ''{0}'' is not allowed
parser.input.nested.too.deep=Input is too deeply nested {0}
parser.count.exceeded=Document parsing count exceeded maximum allowed value of {0}

generator.flush.io.err=I/O error while flushing generated JSON
generator.close.io.err=I/O error while closing JsonGenerator
Expand Down
Loading
Loading