22// Licensed under the Apache License, Version 2.0.
33
44using System ;
5+ using System . Collections . Generic ;
56using System . Threading . Tasks ;
67using Azure . Storage . Blobs ;
78using Microsoft . AspNetCore . Http ;
@@ -22,9 +23,10 @@ public class AzureBlobStorageImageProvider : IImageProvider
2223 private static readonly char [ ] SlashChars = { '\\ ' , '/' } ;
2324
2425 /// <summary>
25- /// The container in the blob service .
26+ /// The containers for the blob services .
2627 /// </summary>
27- private readonly BlobContainerClient container ;
28+ private readonly Dictionary < string , BlobContainerClient > containers
29+ = new Dictionary < string , BlobContainerClient > ( ) ;
2830
2931 /// <summary>
3032 /// The blob storage options.
@@ -55,7 +57,12 @@ public AzureBlobStorageImageProvider(
5557 this . storageOptions = storageOptions . Value ;
5658 this . formatUtilities = formatUtilities ;
5759
58- this . container = new BlobContainerClient ( this . storageOptions . ConnectionString , this . storageOptions . ContainerName ) ;
60+ foreach ( AzureBlobContainerClientOptions container in this . storageOptions . BlobContainers )
61+ {
62+ this . containers . Add (
63+ container . ContainerName ,
64+ new BlobContainerClient ( container . ConnectionString , container . ContainerName ) ) ;
65+ }
5966 }
6067
6168 /// <inheritdoc/>
@@ -74,16 +81,40 @@ public async Task<IImageResolver> GetAsync(HttpContext context)
7481 // Strip the leading slash and container name from the HTTP request path and treat
7582 // the remaining path string as the blob name.
7683 // Path has already been correctly parsed before here.
77- string blobName = context . Request . Path . Value . TrimStart ( SlashChars )
78- . Substring ( this . storageOptions . ContainerName . Length )
79- . TrimStart ( SlashChars ) ;
84+ string containerName = string . Empty ;
85+ BlobContainerClient container = null ;
86+
87+ // We want an exact match here to ensure that container names starting with
88+ // the same prefix are not mixed up.
89+ string path = context . Request . Path . Value . TrimStart ( SlashChars ) ;
90+ int index = path . IndexOfAny ( SlashChars ) ;
91+ string nameToMatch = index != - 1 ? path . Substring ( index ) : path ;
92+
93+ foreach ( string key in this . containers . Keys )
94+ {
95+ if ( nameToMatch . Equals ( key , StringComparison . OrdinalIgnoreCase ) )
96+ {
97+ containerName = key ;
98+ container = this . containers [ key ] ;
99+ break ;
100+ }
101+ }
102+
103+ // Something has gone horribly wrong for this to happen but check anyway.
104+ if ( container is null )
105+ {
106+ return null ;
107+ }
108+
109+ // Blob name should be the remaining path string.
110+ string blobName = path . Substring ( containerName . Length ) . TrimStart ( SlashChars ) ;
80111
81112 if ( string . IsNullOrWhiteSpace ( blobName ) )
82113 {
83114 return null ;
84115 }
85116
86- BlobClient blob = this . container . GetBlobClient ( blobName ) ;
117+ BlobClient blob = container . GetBlobClient ( blobName ) ;
87118
88119 if ( ! await blob . ExistsAsync ( ) )
89120 {
@@ -99,8 +130,18 @@ public bool IsValidRequest(HttpContext context)
99130
100131 private bool IsMatch ( HttpContext context )
101132 {
133+ // Only match loosly here for performance.
134+ // Path matching conflicts should be dealt with by configuration.
102135 string path = context . Request . Path . Value . TrimStart ( SlashChars ) ;
103- return path . StartsWith ( this . storageOptions . ContainerName , StringComparison . OrdinalIgnoreCase ) ;
136+ foreach ( string container in this . containers . Keys )
137+ {
138+ if ( path . StartsWith ( container , StringComparison . OrdinalIgnoreCase ) )
139+ {
140+ return true ;
141+ }
142+ }
143+
144+ return false ;
104145 }
105146 }
106147}
0 commit comments