diff --git a/src/Microsoft.OData.Core/MediaTypeUtils.cs b/src/Microsoft.OData.Core/MediaTypeUtils.cs index a41524e842..bac04e885f 100644 --- a/src/Microsoft.OData.Core/MediaTypeUtils.cs +++ b/src/Microsoft.OData.Core/MediaTypeUtils.cs @@ -56,12 +56,12 @@ internal static class MediaTypeUtils /// /// Max size to cache match info. /// - private const int MatchInfoCacheMaxSize = 256; + private const int MatchInfoCacheInitialSize = 256; /// /// Concurrent cache to cache match info. /// - private static MatchInfoConcurrentCache MatchInfoCache = new MatchInfoConcurrentCache(MatchInfoCacheMaxSize); + private static MatchInfoConcurrentCache MatchInfoCache = new MatchInfoConcurrentCache(MatchInfoCacheInitialSize); /// UTF-8 encoding, without the BOM preamble. /// @@ -413,6 +413,15 @@ internal static ODataFormat GetFormatFromContentType(string contentTypeName, ODa throw new ODataContentTypeException(Strings.MediaTypeUtils_CannotDetermineFormatFromContentType(str, contentTypeName)); } + /// + /// Internal method for testing membership of the cache + /// + /// The ContentTypes in the cache in the MediaInfoCache + internal static IEnumerable GetCacheKeys() + { + return MatchInfoCache.GetKeys(); + } + /// /// Parses the specified content type header into a media type instance. /// @@ -908,7 +917,7 @@ public MatchInfoCacheKey(ODataMediaTypeResolver resolver, ODataPayloadKind paylo { this.MediaTypeResolver = resolver; this.PayloadKind = payloadKind; - this.ContentTypeName = contentTypeName; + this.ContentTypeName = RemoveBoundary(contentTypeName); } /// @@ -924,7 +933,7 @@ public MatchInfoCacheKey(ODataMediaTypeResolver resolver, ODataPayloadKind paylo /// /// Name of content type. /// - private string ContentTypeName { get; set; } + internal string ContentTypeName { get; set; } /// /// Returns a value indicating whether this instance is equal to a specified object. @@ -967,6 +976,27 @@ public override int GetHashCode() int result = this.MediaTypeResolver.GetHashCode() ^ this.PayloadKind.GetHashCode(); return this.ContentTypeName != null ? result ^ this.ContentTypeName.GetHashCode() : result; } + + /// + /// Multipart/mixed content types have a boundary that varies by request. + /// It should not be used as part of the format key. + /// + /// + /// + private static string RemoveBoundary(string contentTypeName) + { + if (contentTypeName.StartsWith("multipart", StringComparison.OrdinalIgnoreCase)) + { + // our formatters don't care about parameters for multipart, so just strip them all out for simplicity + int parameter = contentTypeName.IndexOf(';'); + if (parameter > 0) + { + return contentTypeName.Substring(0, parameter); + } + } + + return contentTypeName; + } } /// @@ -982,10 +1012,10 @@ private sealed class MatchInfoConcurrentCache /// /// Constructor. /// - /// Max size of the elements that the cache can contain. - public MatchInfoConcurrentCache(int maxSize) + /// Initial size of the cache. + public MatchInfoConcurrentCache(int initialSize) { - this.dict = new ConcurrentDictionary(4, maxSize); + this.dict = new ConcurrentDictionary(4, initialSize); } /// @@ -1018,6 +1048,15 @@ public void Add(MatchInfoCacheKey key, MediaTypeMatchInfo value) this.dict.TryAdd(key, value); } } + + /// + /// Internal method for validating expected elements in the cache + /// + /// ContentType of items in the cache + internal IEnumerable GetKeys() + { + return this.dict.Keys.Select(k => k.ContentTypeName); + } } } } diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/MediaTypeUtilsTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/MediaTypeUtilsTests.cs index 519f411b64..726eaf7170 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/MediaTypeUtilsTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/MediaTypeUtilsTests.cs @@ -434,6 +434,20 @@ public void AlterContentTypeForJsonPaddingIfNeededShouldFailIfAppJsonIsNotAtStar Action target = () => MediaTypeUtils.AlterContentTypeForJsonPadding(original); target.Throws(Strings.ODataMessageWriter_JsonPaddingOnInvalidContentType("tricky/application/json")); } + + [Fact] + public void MultipartBoundariesShouldNotGrowCache() + { + for (int i = 1; i < 100; i++) + { + ODataMediaType mediaType; + Encoding encoding; + ODataPayloadKind payloadKind; + MediaTypeUtils.GetFormatFromContentType(string.Format("multipart/mixed;boundary={0}", Guid.NewGuid()), new ODataPayloadKind[] { ODataPayloadKind.Batch }, ODataMediaTypeResolver.GetMediaTypeResolver(null), out mediaType, out encoding, out payloadKind); + } + + Assert.True(MediaTypeUtils.GetCacheKeys().Count(k => k.StartsWith("multipart/mixed")) == 1, "Multiple multipart/mixed keys in cache"); + } } internal class TestMediaTypeWithFormat