@@ -36,6 +36,9 @@ use crate::streams::TransferStream;
3636/// The Azure Blob Storage domain suffix.
3737const AZURE_BLOB_STORAGE_ROOT_DOMAIN : & str = "blob.core.windows.net" ;
3838
39+ /// The Azurite root domain suffix.
40+ const AZURITE_ROOT_DOMAIN : & str = "blob.core.windows.net.localhost" ;
41+
3942/// The default block size in bytes (4 MiB).
4043const DEFAULT_BLOCK_SIZE : u64 = 4 * ONE_MEBIBYTE ;
4144
@@ -94,66 +97,6 @@ pub enum AzureError {
9497 BlobNameMissing ,
9598}
9699
97- /// Determines if the given URL is an Azure Blob Storage URL.
98- pub fn is_azure_url ( url : & Url ) -> bool {
99- match url. scheme ( ) {
100- "az" => true ,
101- "https" => {
102- let Some ( domain) = url. domain ( ) else {
103- return false ;
104- } ;
105-
106- // Virtual host style URL of the form https://<account>.blob.core.windows.net/<container>/<path>
107- let Some ( ( _, domain) ) = domain. split_once ( '.' ) else {
108- return false ;
109- } ;
110- domain. eq_ignore_ascii_case ( AZURE_BLOB_STORAGE_ROOT_DOMAIN )
111- }
112- _ => false ,
113- }
114- }
115-
116- /// Rewrites an Azure Blob Storage URL (az://) into a HTTPS URL.
117- ///
118- /// If the URL is not `az` schemed, the given URL is returned as-is.
119- pub fn rewrite_url ( url : Url ) -> Result < Url > {
120- match url. scheme ( ) {
121- "az" => {
122- let account = url. host_str ( ) . ok_or ( AzureError :: InvalidScheme ) ?;
123-
124- if url. path ( ) == "/" {
125- return Err ( AzureError :: InvalidScheme . into ( ) ) ;
126- }
127-
128- match ( url. query ( ) , url. fragment ( ) ) {
129- ( None , None ) => format ! (
130- "https://{account}.{AZURE_BLOB_STORAGE_ROOT_DOMAIN}{path}" ,
131- path = url. path( )
132- ) ,
133- ( None , Some ( fragment) ) => {
134- format ! (
135- "https://{account}.{AZURE_BLOB_STORAGE_ROOT_DOMAIN}{path}#{fragment}" ,
136- path = url. path( )
137- )
138- }
139- ( Some ( query) , None ) => format ! (
140- "https://{account}.{AZURE_BLOB_STORAGE_ROOT_DOMAIN}{path}?{query}" ,
141- path = url. path( )
142- ) ,
143- ( Some ( query) , Some ( fragment) ) => {
144- format ! (
145- "https://{account}.{AZURE_BLOB_STORAGE_ROOT_DOMAIN}{path}?{query}#{fragment}" ,
146- path = url. path( )
147- )
148- }
149- }
150- . parse ( )
151- . map_err ( |_| AzureError :: InvalidScheme . into ( ) )
152- }
153- _ => Ok ( url) ,
154- }
155- }
156-
157100/// Represents information about a blob.
158101#[ derive( Debug , Deserialize ) ]
159102struct Blob {
@@ -421,6 +364,69 @@ impl StorageBackend for AzureBlobStorageBackend {
421364 Ok ( block_size)
422365 }
423366
367+ fn is_supported_url ( config : & Config , url : & Url ) -> bool {
368+ match url. scheme ( ) {
369+ "az" => true ,
370+ "http" | "https" => {
371+ let Some ( domain) = url. domain ( ) else {
372+ return false ;
373+ } ;
374+
375+ // Virtual host style URL of the form https://<account>.blob.core.windows.net/<container>/<path>
376+ let Some ( ( _, domain) ) = domain. split_once ( '.' ) else {
377+ return false ;
378+ } ;
379+
380+ domain. eq_ignore_ascii_case ( AZURE_BLOB_STORAGE_ROOT_DOMAIN )
381+ | ( config. azure . use_azurite && domain. eq_ignore_ascii_case ( AZURITE_ROOT_DOMAIN ) )
382+ }
383+ _ => false ,
384+ }
385+ }
386+
387+ fn rewrite_url ( & self , url : Url ) -> Result < Url > {
388+ match url. scheme ( ) {
389+ "az" => {
390+ let account = url. host_str ( ) . ok_or ( AzureError :: InvalidScheme ) ?;
391+
392+ if url. path ( ) == "/" {
393+ return Err ( AzureError :: InvalidScheme . into ( ) ) ;
394+ }
395+
396+ let ( scheme, root, port) = if self . config . azure . use_azurite {
397+ ( "http" , AZURITE_ROOT_DOMAIN , ":10000" )
398+ } else {
399+ ( "https" , AZURE_BLOB_STORAGE_ROOT_DOMAIN , "" )
400+ } ;
401+
402+ match ( url. query ( ) , url. fragment ( ) ) {
403+ ( None , None ) => {
404+ format ! ( "{scheme}://{account}.{root}{port}{path}" , path = url. path( ) )
405+ }
406+ ( None , Some ( fragment) ) => {
407+ format ! (
408+ "{scheme}://{account}.{root}{port}{path}#{fragment}" ,
409+ path = url. path( )
410+ )
411+ }
412+ ( Some ( query) , None ) => format ! (
413+ "{scheme}://{account}.{root}{port}{path}?{query}" ,
414+ path = url. path( )
415+ ) ,
416+ ( Some ( query) , Some ( fragment) ) => {
417+ format ! (
418+ "{scheme}://{account}.{root}{port}{path}?{query}#{fragment}" ,
419+ path = url. path( )
420+ )
421+ }
422+ }
423+ . parse ( )
424+ . map_err ( |_| AzureError :: InvalidScheme . into ( ) )
425+ }
426+ _ => Ok ( url) ,
427+ }
428+ }
429+
424430 fn join_url < ' a > ( & self , mut url : Url , segments : impl Iterator < Item = & ' a str > ) -> Result < Url > {
425431 let mut segments = segments. peekable ( ) ;
426432
@@ -446,8 +452,9 @@ impl StorageBackend for AzureBlobStorageBackend {
446452
447453 async fn head ( & self , url : Url ) -> Result < Response > {
448454 debug_assert ! (
449- is_azure_url( & url) && url. scheme( ) == "https" ,
450- "expected Azure HTTPS URL"
455+ Self :: is_supported_url( & self . config, & url) ,
456+ "{url} is not a supported Azure URL" ,
457+ url = url. as_str( )
451458 ) ;
452459
453460 debug ! ( "sending HEAD request for `{url}`" , url = url. display( ) ) ;
@@ -470,8 +477,9 @@ impl StorageBackend for AzureBlobStorageBackend {
470477
471478 async fn get ( & self , url : Url ) -> Result < Response > {
472479 debug_assert ! (
473- is_azure_url( & url) && url. scheme( ) == "https" ,
474- "expected Azure HTTPS URL"
480+ Self :: is_supported_url( & self . config, & url) ,
481+ "{url} is not a supported Azure URL" ,
482+ url = url. as_str( )
475483 ) ;
476484
477485 debug ! ( "sending GET request for `{url}`" , url = url. display( ) ) ;
@@ -494,8 +502,9 @@ impl StorageBackend for AzureBlobStorageBackend {
494502
495503 async fn get_range ( & self , url : Url , etag : & str , range : Range < u64 > ) -> Result < Response > {
496504 debug_assert ! (
497- is_azure_url( & url) && url. scheme( ) == "https" ,
498- "expected Azure HTTPS URL"
505+ Self :: is_supported_url( & self . config, & url) ,
506+ "{url} is not a supported Azure URL" ,
507+ url = url. as_str( )
499508 ) ;
500509
501510 debug ! (
@@ -541,8 +550,9 @@ impl StorageBackend for AzureBlobStorageBackend {
541550
542551 async fn walk ( & self , url : Url ) -> Result < Vec < String > > {
543552 debug_assert ! (
544- is_azure_url( & url) && url. scheme( ) == "https" ,
545- "expected Azure HTTPS URL"
553+ Self :: is_supported_url( & self . config, & url) ,
554+ "{url} is not a supported Azure URL" ,
555+ url = url. as_str( )
546556 ) ;
547557
548558 debug ! ( "walking `{url}` as a directory" , url = url. display( ) ) ;
@@ -671,8 +681,9 @@ impl StorageBackend for AzureBlobStorageBackend {
671681
672682 async fn new_upload ( & self , url : Url ) -> Result < Self :: Upload > {
673683 debug_assert ! (
674- is_azure_url( & url) && url. scheme( ) == "https" ,
675- "expected Azure HTTPS URL"
684+ Self :: is_supported_url( & self . config, & url) ,
685+ "{url} is not a supported Azure URL" ,
686+ url = url. as_str( )
676687 ) ;
677688
678689 Ok ( AzureBlobUpload :: new (
0 commit comments