Merge branch '3.4.x'
	
		
			
	
		
	
	
		
			
				
	
				Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run
				
					Details
				
			
		
			
				
	
				Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions
				
					Details
				
			
		
			
				
	
				Build and Deploy Snapshot / Verify (push) Blocked by required conditions
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[early-access:true toolchain:true version:24], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[early-access:true toolchain:true version:24], map[id:windows-latest name:Windows]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:windows-latest name:Windows]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:21], map[id:windows-latest name:Windows]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:22], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:22], map[id:windows-latest name:Windows]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:windows-latest name:Windows]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:false version:17]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run
				
					Details
				
			
		
	
				
					
				
			
				
	
				Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run
				
					Details
				
			
		
			
				
	
				Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions
				
					Details
				
			
		
			
				
	
				Build and Deploy Snapshot / Verify (push) Blocked by required conditions
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[early-access:true toolchain:true version:24], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[early-access:true toolchain:true version:24], map[id:windows-latest name:Windows]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:windows-latest name:Windows]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:21], map[id:windows-latest name:Windows]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:22], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:22], map[id:windows-latest name:Windows]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:windows-latest name:Windows]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:false version:17]) (push) Waiting to run
				
					Details
				
			
		
			
				
	
				Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run
				
					Details
				
			
		
	Closes gh-45262
This commit is contained in:
		
						commit
						c6c2ce2eda
					
				| 
						 | 
				
			
			@ -47,8 +47,12 @@ import org.springframework.util.function.ThrowingConsumer;
 | 
			
		|||
 */
 | 
			
		||||
class JsonValueWriter {
 | 
			
		||||
 | 
			
		||||
	private static final int DEFAULT_MAX_NESTING_DEPTH = 500;
 | 
			
		||||
 | 
			
		||||
	private final Appendable out;
 | 
			
		||||
 | 
			
		||||
	private final int maxNestingDepth;
 | 
			
		||||
 | 
			
		||||
	private MemberPath path = MemberPath.ROOT;
 | 
			
		||||
 | 
			
		||||
	private final Deque<JsonWriterFiltersAndProcessors> filtersAndProcessors = new ArrayDeque<>();
 | 
			
		||||
| 
						 | 
				
			
			@ -60,7 +64,18 @@ class JsonValueWriter {
 | 
			
		|||
	 * @param out the {@link Appendable} used to receive the JSON output
 | 
			
		||||
	 */
 | 
			
		||||
	JsonValueWriter(Appendable out) {
 | 
			
		||||
		this(out, DEFAULT_MAX_NESTING_DEPTH);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Create a new {@link JsonValueWriter} instance.
 | 
			
		||||
	 * @param out the {@link Appendable} used to receive the JSON output
 | 
			
		||||
	 * @param maxNestingDepth the maximum allowed nesting depth for JSON objects and
 | 
			
		||||
	 * arrays
 | 
			
		||||
	 */
 | 
			
		||||
	JsonValueWriter(Appendable out, int maxNestingDepth) {
 | 
			
		||||
		this.out = out;
 | 
			
		||||
		this.maxNestingDepth = maxNestingDepth;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void pushProcessors(JsonWriterFiltersAndProcessors jsonProcessors) {
 | 
			
		||||
| 
						 | 
				
			
			@ -115,10 +130,7 @@ class JsonValueWriter {
 | 
			
		|||
				throw new UncheckedIOException(ex);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else if (value instanceof Path p) {
 | 
			
		||||
			writeString(p.toString());
 | 
			
		||||
		}
 | 
			
		||||
		else if (value instanceof Iterable<?> iterable) {
 | 
			
		||||
		else if (value instanceof Iterable<?> iterable && canWriteAsArray(iterable)) {
 | 
			
		||||
			writeArray(iterable::forEach);
 | 
			
		||||
		}
 | 
			
		||||
		else if (ObjectUtils.isArray(value)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -135,6 +147,10 @@ class JsonValueWriter {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private <V> boolean canWriteAsArray(Iterable<?> iterable) {
 | 
			
		||||
		return !(iterable instanceof Path);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Start a new {@link Series} (JSON object or array).
 | 
			
		||||
	 * @param series the series to start
 | 
			
		||||
| 
						 | 
				
			
			@ -144,6 +160,10 @@ class JsonValueWriter {
 | 
			
		|||
	 */
 | 
			
		||||
	void start(Series series) {
 | 
			
		||||
		if (series != null) {
 | 
			
		||||
			int nestingDepth = this.activeSeries.size();
 | 
			
		||||
			Assert.state(nestingDepth <= this.maxNestingDepth,
 | 
			
		||||
					() -> "JSON nesting depth (%s) exceeds maximum depth of %s (current path: %s)"
 | 
			
		||||
						.formatted(nestingDepth, this.maxNestingDepth, this.path));
 | 
			
		||||
			this.activeSeries.push(new ActiveSeries(series));
 | 
			
		||||
			append(series.openChar);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,7 @@ package org.springframework.boot.json;
 | 
			
		|||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.nio.file.Path;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.LinkedHashMap;
 | 
			
		||||
import java.util.LinkedHashSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
| 
						 | 
				
			
			@ -253,6 +254,36 @@ class JsonValueWriterTests {
 | 
			
		|||
			.isEqualTo(quoted("a\\%1$sb\\%1$sc".formatted(File.separator)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void illegalStateExceptionShouldBeThrownWhenCollectionExceededNestingDepth() {
 | 
			
		||||
		JsonValueWriter writer = new JsonValueWriter(new StringBuilder(), 128);
 | 
			
		||||
		List<Object> list = new ArrayList<>();
 | 
			
		||||
		list.add(list);
 | 
			
		||||
		assertThatIllegalStateException().isThrownBy(() -> writer.write(list))
 | 
			
		||||
			.withMessageStartingWith(
 | 
			
		||||
					"JSON nesting depth (129) exceeds maximum depth of 128 (current path: [0][0][0][0][0][0][0][0][0][0][0][0]");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void illegalStateExceptionShouldBeThrownWhenMapExceededNestingDepth() {
 | 
			
		||||
		JsonValueWriter writer = new JsonValueWriter(new StringBuilder(), 128);
 | 
			
		||||
		Map<String, Object> map = new LinkedHashMap<>();
 | 
			
		||||
		map.put("foo", Map.of("bar", map));
 | 
			
		||||
		assertThatIllegalStateException().isThrownBy(() -> writer.write(map))
 | 
			
		||||
			.withMessageStartingWith(
 | 
			
		||||
					"JSON nesting depth (129) exceeds maximum depth of 128 (current path: foo.bar.foo.bar.foo.bar.foo");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void illegalStateExceptionShouldBeThrownWhenIterableExceededNestingDepth() {
 | 
			
		||||
		JsonValueWriter writer = new JsonValueWriter(new StringBuilder(), 128);
 | 
			
		||||
		List<Object> list = new ArrayList<>();
 | 
			
		||||
		list.add(list);
 | 
			
		||||
		assertThatIllegalStateException().isThrownBy(() -> writer.write((Iterable<Object>) list::iterator))
 | 
			
		||||
			.withMessageStartingWith(
 | 
			
		||||
					"JSON nesting depth (129) exceeds maximum depth of 128 (current path: [0][0][0][0][0][0][0][0][0][0][0][0]");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private <V> String write(V value) {
 | 
			
		||||
		return doWrite((valueWriter) -> valueWriter.write(value));
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue