From 1956cb1e57aa0574bf18af3399dc25a5dfaeb1dd Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 11 Jun 2019 20:57:17 +0200 Subject: [PATCH] Defensive concurrent access to shared file extension data structures Closes gh-23064 --- ...MappingMediaTypeFileExtensionResolver.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java b/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java index 6b7208bc3c..f6ec22df6e 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java +++ b/spring-web/src/main/java/org/springframework/web/accept/MappingMediaTypeFileExtensionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,11 +23,10 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; import org.springframework.http.MediaType; import org.springframework.lang.Nullable; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; /** * An implementation of {@code MediaTypeFileExtensionResolver} that maintains @@ -37,15 +36,16 @@ import org.springframework.util.MultiValueMap; * Subsequently subclasses can use {@link #addMapping} to add more mappings. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 3.2 */ public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExtensionResolver { private final ConcurrentMap mediaTypes = new ConcurrentHashMap<>(64); - private final MultiValueMap fileExtensions = new LinkedMultiValueMap<>(); + private final ConcurrentMap> fileExtensions = new ConcurrentHashMap<>(64); - private final List allFileExtensions = new ArrayList<>(); + private final List allFileExtensions = new CopyOnWriteArrayList<>(); /** @@ -53,12 +53,14 @@ public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExten */ public MappingMediaTypeFileExtensionResolver(@Nullable Map mediaTypes) { if (mediaTypes != null) { + List allFileExtensions = new ArrayList<>(); mediaTypes.forEach((extension, mediaType) -> { String lowerCaseExtension = extension.toLowerCase(Locale.ENGLISH); this.mediaTypes.put(lowerCaseExtension, mediaType); - this.fileExtensions.add(mediaType, lowerCaseExtension); - this.allFileExtensions.add(lowerCaseExtension); + addFileExtension(mediaType, extension); + allFileExtensions.add(lowerCaseExtension); }); + this.allFileExtensions.addAll(allFileExtensions); } } @@ -77,11 +79,17 @@ public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExten protected void addMapping(String extension, MediaType mediaType) { MediaType previous = this.mediaTypes.putIfAbsent(extension, mediaType); if (previous == null) { - this.fileExtensions.add(mediaType, extension); + addFileExtension(mediaType, extension); this.allFileExtensions.add(extension); } } + private void addFileExtension(MediaType mediaType, String extension) { + List newList = new CopyOnWriteArrayList<>(); + List oldList = this.fileExtensions.putIfAbsent(mediaType, newList); + (oldList != null ? oldList : newList).add(extension); + } + @Override public List resolveFileExtensions(MediaType mediaType) {