char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
51266f9
main
1use std:: cell:: RefCell ; 2use std:: collections::{ BTreeMap , BTreeSet , HashMap }; 3use std:: io:: Read ; 4use std:: path::{ Path , PathBuf }; 5use std:: rc:: Rc ; 6 7use crate :: authority:: SourceAuthority ; 8use crate :: caps:: PathTarget ; 9use crate :: package_config::{ PackageConfig , Source }; 10use avril_core:: Error ; 11 12/// Which effects a resolution step may perform. `Acquire` may bind 13/// packages, load remote configs, and prepare npm roots; `PreparedOnly` 14/// uses only already-prepared metadata and never performs I/O. 15/// Shared by `LocalPackages`, `RemotePackages`, and `ModuleGraph` so the 16/// decision tree branches on one mode type at the edges only. 17# [ derive ( Clone , Copy , PartialEq , Eq , Debug )] 18pub enum ResolveMode { 19Acquire , 20PreparedOnly , 21} 22 23impl ResolveMode { 24/// `resolve_target` and export probing allow missing files exactly in 25/// prepared-only metadata resolution. 26pub fn allow_missing ( self ) ->bool { 27matches! ( self , Self :: PreparedOnly ) 28} 29} 30 31struct Package { 32target : PathTarget , 33config : PackageConfig , 34bindings : RefCell < Option < BTreeMap < String , Source >>>, 35source : String , 36} 37 38/// Shared prefix of local classification: the owning package plus what its 39/// own `package_mapping` selects, before any binding acquisition. 40enum LocalStep { 41Done ( Resolution ), 42Export ( Rc < Package >, String ), 43Bindings ( Rc < Package >), 44} 45 46pub enum Resolution { 47Specifier ( String ), 48Module ( String ), 49Remote ( String ), 50} 51 52pub ( crate ) enum PackageMapping { 53Specifier ( String ), 54Export ( String ), 55} 56 57pub ( crate ) fn package_mapping ( 58config : & PackageConfig , 59specifier : & str , 60referrer : & url:: Url , 61) ->Result < Option < PackageMapping >, String > { 62if let Some ( target) = config 63. import_map 64. resolve ( specifier, referrer) 65. map_err ( |error| error. to_string ()) ? 66{ 67return Ok ( Some ( PackageMapping :: Specifier ( target))); 68} 69if let Some ( name) =& config. name 70 &&let Some ( key) =export_key ( specifier, name) 71{ 72return Ok ( Some ( PackageMapping :: Export ( key))); 73} 74Ok ( config 75. registry_map 76. resolve ( specifier, referrer) 77. map_err ( |error| error. to_string ()) ? 78. map ( PackageMapping :: Specifier )) 79} 80 81pub ( crate ) fn select_binding ( 82bindings : Option < & BTreeMap < String , Source >>, 83specifier : & str , 84) ->Option <( String , Source )> { 85 bindings 86. into_iter () 87. flatten () 88. find_map ( |( binding, source) |{ 89export_key ( specifier, binding). map ( |key|( key, source. clone ())) 90}) 91} 92 93pub struct LocalPackages { 94pub ( crate ) config_inputs : RefCell < BTreeSet < PathBuf >>, 95authority : SourceAuthority , 96directories : RefCell < HashMap < PathBuf , Option < Rc < Package >>>>, 97tsconfigs : RefCell < HashMap < PathBuf , crate :: project:: JsxConfig >>, 98} 99 100impl LocalPackages { 101pub fn new ( authority : SourceAuthority ) ->Self { 102Self { 103 authority, 104config_inputs : RefCell :: default (), 105directories : RefCell :: new ( HashMap :: new ()), 106tsconfigs : RefCell :: new ( HashMap :: new ()), 107} 108} 109 110fn package ( & self , referrer : & str ) ->Result < Option < Rc < Package >>, Error > { 111let referrer =Path :: new ( referrer); 112let mut directory = referrer 113. parent () 114. ok_or_else ( ||Error :: Module ( "Expected an absolute referrer path" . into ())) ?; 115let package =loop { 116// The reserved `.avril/npm` tree is never a config authority, 117// even if a published archive plants an avril.conf inside it. 118// Other `.avril` data (editor output, gitignore) keeps the 119// existing discovery behavior. 120if under_npm_tree ( directory) { 121match directory. parent () { 122Some ( parent) =>{ 123 directory = parent; 124continue ; 125} 126None =>break None , 127} 128} 129let target =PathTarget :: resolve ( directory) 130. map_err ( |_|Error :: Module ( "Cannot discover package" . into ())) ?; 131if target. as_path () != directory ||self . authority . check_local ( & target). is_err () { 132break None ; 133} 134let handle =crate :: sources:: open_directory ( directory) 135. map_err ( |_|Error :: Module ( "Cannot discover package" . into ())) ?; 136if !trusted ( 137& handle 138. dir_metadata () 139. map_err ( |_|Error :: Module ( "Cannot inspect package directory" . into ())) ?, 140) { 141break None ; 142} 143let path = directory. join ( "avril.conf" ); 144let target =PathTarget :: for_entry ( & path) 145. map_err ( |_|Error :: Module ( "Cannot discover package" . into ())) ?; 146// A denial must not disclose whether the config (or a symlink referent) exists. 147if self . authority . check_local ( & target). is_err () { 148break None ; 149} 150let package =self . load ( directory, & handle, target, false ) ?; 151if package. is_some () { 152break package; 153} 154match directory. parent () { 155Some ( parent) => directory = parent, 156None =>break None , 157} 158}; 159Ok ( package) 160} 161 162fn load ( 163& self , 164directory : & Path , 165handle : & cap_std:: fs:: Dir , 166target : PathTarget , 167required : bool , 168) ->Result < Option < Rc < Package >>, Error > { 169if target. as_path (). parent () !=Some ( directory) { 170return Err ( Error :: Module ( 171"Config directory changed during discovery" . into (), 172)); 173} 174let cached =self . directories . borrow (). get ( directory). cloned (); 175if let Some ( Some ( package)) = cached{ 176self . authority 177. check_local ( & package. target ) 178. map_err ( |error|Error :: Module ( error. to_string ())) ?; 179return Ok ( Some ( package)); 180} 181if matches! ( cached, Some ( None )) { 182return if required{ 183Err ( Error :: Module ( format! ( 184"Dependency directory must contain avril.conf: {}" , 185 directory. display () 186))) 187} else { 188Ok ( None ) 189}; 190} 191let path = target. as_path (); 192self . authority 193. check_local ( & target) 194. map_err ( |error|Error :: Module ( error. to_string ())) ?; 195let package = 196match read_config ( handle, path). map_err ( |error|Error :: Module ( error. to_string ())) ?{ 197Some ( source) =>{ 198self . authority 199. check_local ( & target) 200. map_err ( |error|Error :: Module ( error. to_string ())) ?; 201let base = url:: Url :: from_file_path ( path) 202. map_err ( |_|Error :: Module ( "Config paths must be absolute" . into ())) ?; 203let render = |error : crate :: config:: Error |{ 204Error :: Module ( error. render ( & path. to_string_lossy (), & source)) 205}; 206let config =PackageConfig :: parse ( & source, & base). map_err ( render) ?; 207self . authority 208. check_local ( & target) 209. map_err ( |error|Error :: Module ( error. to_string ())) ?; 210Some ( Rc :: new ( Package { 211 target, 212 config, 213bindings : RefCell :: default (), 214 source, 215})) 216} 217None if required =>{ 218return Err ( Error :: Module ( format! ( 219"Dependency directory must contain avril.conf: {}" , 220 directory. display () 221))); 222} 223None =>None , 224}; 225self . directories 226. borrow_mut () 227. insert ( directory. to_owned (), package. clone ()); 228Ok ( package) 229} 230 231fn dependency ( & self , directory : & Path , specifier : & str ) ->Result < Rc < Package >, Error > { 232let target = 233PathTarget :: resolve ( directory). map_err ( |error|self . path_error ( specifier, error)) ?; 234self . authority 235. check_local ( & target) 236. map_err ( |error|self . path_error ( specifier, error)) ?; 237let directory = target. as_path (); 238let handle =crate :: sources:: open_directory ( directory) 239. map_err ( |error|self . path_error ( specifier, error)) ?; 240if !trusted ( 241& handle 242. dir_metadata () 243. map_err ( |error|self . path_error ( specifier, error)) ?, 244) { 245return Err ( Error :: Module ( format! ( 246"Untrusted dependency directory: {specifier}" 247))); 248} 249let target =PathTarget :: for_entry ( directory. join ( "avril.conf" )) 250. map_err ( |error|self . path_error ( specifier, error)) ?; 251self . authority 252. check_local ( & target) 253. map_err ( |error|self . path_error ( specifier, error)) ?; 254let package =self . load ( directory, & handle, target, true ) ?. unwrap (); 255if package. config . exports . is_empty () { 256return Err ( Error :: Module ( format! ( 257"Dependency {specifier:?} has no exports: {}" , 258 package. target . as_path (). display () 259))); 260} 261Ok ( package) 262} 263 264fn path_error ( & self , specifier : & str , error : impl std:: fmt:: Display ) ->Error { 265if self . authority . unrestricted_local () { 266Error :: Module ( format! ( "{specifier}: {error}" )) 267} else if self . authority . is_read () { 268Error :: Module ( format! ( "PermissionDenied: read {specifier}" )) 269} else { 270Error :: Module ( format! ( "PermissionDenied: js-exec {specifier}" )) 271} 272} 273 274/// Loads the package owning `referrer`, binding its dependencies before any guest code runs. 275pub async fn initialize ( 276& self , 277referrer : & str , 278remote : & crate :: remote_packages:: RemotePackages , 279) ->Result <(), Error > { 280if let Some ( package) =self . package ( referrer) ?{ 281self . bind ( & package, remote). await ?; 282} 283Ok (()) 284} 285 286async fn bind ( 287& self , 288package : & Package , 289remote : & crate :: remote_packages:: RemotePackages , 290) ->Result <(), Error > { 291if package. bindings . borrow (). is_none () { 292let bindings = remote. bindings ( & package. config ). await . map_err ( |error|{ 293Error :: Module ( 294 error. render ( & package. target . as_path (). to_string_lossy (), & package. source ), 295) 296}) ?; 297self . authority 298. check_local ( & package. target ) 299. map_err ( |error|Error :: Module ( error. to_string ())) ?; 300* package. bindings . borrow_mut () =Some ( bindings); 301} 302Ok (()) 303} 304 305pub fn type_override ( 306& self , 307specifier : & str , 308referrer : & str , 309owner : & str , 310) ->Result < Option < String >, Error > { 311let Some ( package) =self . package ( owner) ?else { 312return Ok ( None ); 313}; 314self . authority 315. check_local ( & package. target ) 316. map_err ( |error|Error :: Module ( error. to_string ())) ?; 317self . config_inputs 318. borrow_mut () 319. insert ( package. target . as_path (). to_owned ()); 320let referrer_url = url:: Url :: from_file_path ( referrer) 321. map_err ( |_|Error :: Module ( "Referrers must be absolute paths" . into ())) ?; 322let result = package 323. config 324. types 325. imports 326. resolve ( specifier, & referrer_url) 327. map_err ( |error|Error :: Module ( error. to_string ())) ?; 328self . authority 329. check_local ( & package. target ) 330. map_err ( |error|Error :: Module ( error. to_string ())) ?; 331Ok ( result) 332} 333 334pub fn type_globals ( & self , referrer : & str ) ->Result < Vec < String >, Error > { 335let Some ( package) =self . package ( referrer) ?else { 336return Ok ( Vec :: new ()); 337}; 338self . authority 339. check_local ( & package. target ) 340. map_err ( |error|Error :: Module ( error. to_string ())) ?; 341self . config_inputs 342. borrow_mut () 343. insert ( package. target . as_path (). to_owned ()); 344let globals = package. config . types . globals . clone (); 345self . authority 346. check_local ( & package. target ) 347. map_err ( |error|Error :: Module ( error. to_string ())) ?; 348Ok ( globals) 349} 350 351pub fn jsx_config ( & self , referrer : & str ) ->Result < crate :: project:: JsxConfig , Error > { 352let package =self . package ( referrer) ?; 353let boundary = package 354. as_ref () 355. and_then ( |package| package. target . as_path (). parent ()); 356crate :: project:: jsx_config ( 357& self . authority , 358Path :: new ( referrer), 359 boundary, 360& package 361. as_ref () 362. map ( |package| package. config . compiler_options . clone ()) 363. unwrap_or_default (), 364& self . config_inputs , 365& self . tsconfigs , 366) 367} 368 369pub fn root ( & self , referrer : & str ) ->Result < PathBuf , Error > { 370if self . authority . is_read () { 371self . root_for_repl ( referrer) 372} else { 373self . discover_root ( referrer, false ) 374} 375} 376 377pub fn root_for_repl ( & self , referrer : & str ) ->Result < PathBuf , Error > { 378let fallback =Path :: new ( referrer) 379. parent () 380. ok_or_else ( ||Error :: Module ( "Expected a REPL directory" . into ())) ?; 381Ok ( self 382. package ( referrer) ? 383. and_then ( |package| package. target . as_path (). parent (). map ( Path :: to_owned)) 384. unwrap_or_else ( || fallback. to_owned ())) 385} 386 387/// The trusted config a REPL session belongs to, through the same 388/// discovery and trust checks package loading applies. 389pub fn config_for_repl ( & self , referrer : & str ) ->Result < Option < PathBuf >, Error > { 390Ok ( self 391. package ( referrer) ? 392. map ( |package| package. target . as_path (). to_owned ())) 393} 394 395pub fn root_for_add ( & self , referrer : & str ) ->Result < PathBuf , Error > { 396self . discover_root ( referrer, true ) 397} 398 399fn discover_root ( & self , referrer : & str , strict : bool ) ->Result < PathBuf , Error > { 400let fallback =Path :: new ( referrer) 401. parent () 402. ok_or_else ( ||Error :: Module ( "Expected an entry directory" . into ())) ?; 403let mut directory =Some ( fallback); 404while let Some ( path) = directory{ 405if under_npm_tree ( path) { 406 directory = path. parent (); 407continue ; 408} 409let handle =crate :: sources:: open_directory ( path) 410. map_err ( |error|Error :: Module ( error. to_string ())) ?; 411if !trusted ( 412& handle 413. dir_metadata () 414. map_err ( |error|Error :: Module ( error. to_string ())) ?, 415) { 416break ; 417} 418match handle. symlink_metadata ( "avril.conf" ) { 419Ok ( metadata) if metadata. is_file () &&trusted ( & metadata) =>{ 420return Ok ( path. to_owned ()); 421} 422Ok ( _) if strict =>{ 423return Err ( Error :: Module ( format! ( 424"Untrusted config: {}" , 425 path. join ( "avril.conf" ). display () 426))); 427} 428Ok ( _) =>break , 429Err ( error) if error. kind () == std:: io:: ErrorKind :: NotFound =>{} 430Err ( error) =>return Err ( Error :: Module ( error. to_string ())), 431} 432 directory = path. parent (); 433} 434Ok ( fallback. to_owned ()) 435} 436 437/// The `npm:` dependency targets declared by the package owning 438/// `referrer`, for joint graph preparation. Empty without a config. 439pub fn npm_dependencies ( & self , referrer : & str ) ->Result < Vec < String >, Error > { 440let Some ( package) =self . package ( referrer) ?else { 441return Ok ( Vec :: new ()); 442}; 443self . authority 444. check_local ( & package. target ) 445. map_err ( |error|Error :: Module ( error. to_string ())) ?; 446Ok ( package 447. config 448. dependencies 449. iter () 450. filter_map ( |dependency|match & dependency. source { 451Source :: Npm ( request) =>Some ( request. request_key ()), 452 _ =>None , 453}) 454. collect ()) 455} 456 457pub fn remote_dependencies ( & self , referrer : & str ) ->Result < Vec < url:: Url >, Error > { 458let Some ( package) =self . package ( referrer) ?else { 459return Ok ( Vec :: new ()); 460}; 461self . authority 462. check_local ( & package. target ) 463. map_err ( |error|Error :: Module ( error. to_string ())) ?; 464Ok ( package 465. config 466. dependencies 467. iter () 468. filter_map ( |dependency|match & dependency. source { 469Source :: Http ( directory) =>Some ( directory. clone ()), 470 _ =>None , 471}) 472. collect ()) 473} 474 475/// Shared synchronous prefix of both modes: the owning package and 476/// what its own imports/scopes/registry map select. Binding lookup 477/// and remote exports stay mode-specific below. 478fn step ( & self , specifier : & str , referrer : & str ) ->Result < Option < LocalStep >, Error > { 479let Some ( package) =self . package ( referrer) ?else { 480return Ok ( None ); 481}; 482self . authority 483. check_local ( & package. target ) 484. map_err ( |error|Error :: Module ( error. to_string ())) ?; 485let url = url:: Url :: from_file_path ( referrer) 486. map_err ( |_|Error :: Module ( "Expected an absolute referrer path" . into ())) ?; 487match package_mapping ( & package. config , specifier, & url). map_err ( |error|{ 488Error :: Module ( format! ( "{}: {error}" , package. target . as_path (). display ())) 489}) ?{ 490Some ( PackageMapping :: Specifier ( target)) =>{ 491Ok ( Some ( LocalStep :: Done ( Resolution :: Specifier ( target)))) 492} 493Some ( PackageMapping :: Export ( key)) =>Ok ( Some ( LocalStep :: Export ( package, key))), 494None =>Ok ( Some ( LocalStep :: Bindings ( package))), 495} 496} 497 498/// Prepared bindings as-is: shared by both modes once acquisition (if 499/// any) has run. 500fn selected ( & self , package : & Package , specifier : & str ) ->Option <( String , Source )> { 501select_binding ( package. bindings . borrow (). as_ref (), specifier) 502} 503 504pub async fn resolve ( 505& self , 506specifier : & str , 507referrer : & str , 508remote : & crate :: remote_packages:: RemotePackages , 509) ->Result < Option < Resolution >, Error > { 510let step =self . step ( specifier, referrer) ?; 511match step{ 512None =>Ok ( None ), 513Some ( LocalStep :: Done ( resolution)) =>Ok ( Some ( resolution)), 514Some ( LocalStep :: Export ( package, key)) =>self 515. export ( & package, & key, specifier, ResolveMode :: Acquire ) 516. map ( Some ), 517Some ( LocalStep :: Bindings ( package)) =>{ 518self . bind ( & package, remote). await ?; 519match self . selected ( & package, specifier) { 520Some (( key, Source :: Local ( directory))) =>{ 521let target =self . dependency ( & directory, specifier) ?; 522self . export ( & target, & key, specifier, ResolveMode :: Acquire ) 523. map ( Some ) 524} 525Some (( key, Source :: Http ( directory))) =>{ 526 remote. export ( & directory, & key, specifier). await . map ( Some ) 527} 528 _ =>Ok ( None ), 529} 530} 531} 532} 533 534pub fn resolve_import_meta ( 535& self , 536specifier : & str , 537referrer : & str , 538remote : & crate :: remote_packages:: RemotePackages , 539) ->Result < Option < Resolution >, Error > { 540let step =self . step ( specifier, referrer) ?; 541match step{ 542None =>Ok ( None ), 543Some ( LocalStep :: Done ( resolution)) =>Ok ( Some ( resolution)), 544Some ( LocalStep :: Export ( package, key)) =>self 545. export ( & package, & key, specifier, ResolveMode :: PreparedOnly ) 546. map ( Some ), 547Some ( LocalStep :: Bindings ( package)) =>match self . selected ( & package, specifier) { 548Some (( key, Source :: Local ( directory))) =>{ 549let target =self . dependency ( & directory, specifier) ?; 550self . export ( & target, & key, specifier, ResolveMode :: PreparedOnly ) 551. map ( Some ) 552} 553Some (( key, Source :: Http ( directory))) => remote 554. resolve_export_meta ( & directory, & key, specifier) 555. map ( Some ), 556 _ =>Ok ( None ), 557}, 558} 559} 560 561fn export ( 562& self , 563package : & Package , 564key : & str , 565specifier : & str , 566mode : ResolveMode , 567) ->Result < Resolution , Error > { 568let export = package. config . exports . get ( key). ok_or_else ( ||{ 569Error :: Module ( format! ( 570"Package has no export {key:?} for {specifier:?}: {}" , 571 package. target . as_path (). display () 572)) 573}) ?; 574let path = export 575. target 576. to_file_path () 577. map_err ( |_|Error :: Module ( "Expected a local export" . into ())) ?; 578let target =if mode. allow_missing () { 579PathTarget :: for_creation ( path) 580} else { 581PathTarget :: resolve ( path) 582} 583. map_err ( |error|self . path_error ( specifier, error)) ?; 584self . authority 585. check_local ( & target) 586. map_err ( |error|self . path_error ( specifier, error)) ?; 587let directory = package. target . as_path (). parent (). unwrap (); 588if !target. as_path (). starts_with ( directory) { 589return Err ( Error :: Module ( 590crate :: config:: Error { 591kind : crate :: config:: ErrorKind :: Syntax , 592message : "Export target escapes its package" . into (), 593span : export. span . clone (), 594previous : None , 595} 596. render ( & package. target . as_path (). to_string_lossy (), & package. source ), 597)); 598} 599let name = target 600. into_path () 601. into_os_string () 602. into_string () 603. map_err ( |_|Error :: Module ( "Module paths must be valid UTF-8" . into ())) ?; 604self . authority 605. check_local ( & package. target ) 606. map_err ( |error|self . path_error ( specifier, error)) ?; 607Ok ( Resolution :: Module ( name)) 608} 609 610pub fn metadata ( & self , referrer : & str ) ->Result < Option < serde_json:: Value >, Error > { 611let Some ( package) =self . package ( referrer) ?else { 612return Ok ( None ); 613}; 614self . authority 615. check_local ( & package. target ) 616. map_err ( |error|Error :: Module ( error. to_string ())) ?; 617let exports: std:: collections:: BTreeMap < _ , _ > = package 618. config 619. exports 620. iter () 621. map ( |( key, export) |( key, export. target . to_file_path (). unwrap ())) 622. collect (); 623let bindings = package. bindings . borrow (); 624let dependencies: BTreeMap < _ , _ > = bindings 625. as_ref () 626. into_iter () 627. flatten () 628. map ( |( name, source) |match source{ 629Source :: Local ( directory) =>( name, serde_json:: json!( directory)), 630Source :: Jsr ( request) =>( name, serde_json:: json!( request. specifier )), 631Source :: Npm ( request) =>( name, serde_json:: json!( request. request_key ())), 632Source :: Http ( directory) =>( name, serde_json:: json!( directory. as_str ())), 633}) 634. collect (); 635Ok ( Some ( 636 serde_json:: json!({ "config" : url:: Url :: from_file_path ( package. target . as_path ()). ok (). map ( String :: from), "name" : package. config . name , "version" : package. config . version , "description" : package. config . description , "exports" : exports, "dependencies" : dependencies}), 637)) 638} 639} 640 641/// Reads a dependency's declared name as host configuration: guest execution grants do not 642/// apply, but trust checks do. The config is neither bound nor cached, so later guest loads 643/// still pass through `LocalPackages::load` and its capability checks. 644/// Read mode must authorize the config itself, including diagnostic excerpts. 645pub ( crate ) fn declared_name ( 646directory : & Path , 647authority : & SourceAuthority , 648) ->Result < Option < String >, String > { 649let target =if authority. is_read () { 650let target =PathTarget :: for_entry ( directory. join ( "avril.conf" )) 651. map_err ( |error| error. to_string ()) ?; 652 authority 653. check_local ( & target) 654. map_err ( |error| error. to_string ()) ?; 655Some ( target) 656} else { 657None 658}; 659let path =match & target{ 660Some ( target) => target. as_path (). to_owned (), 661None =>PathTarget :: resolve ( directory) 662. map_err ( |error| error. to_string ()) ? 663. into_path () 664. join ( "avril.conf" ), 665}; 666let directory = path. parent (). unwrap (); 667let handle =crate :: sources:: open_directory ( directory). map_err ( |error| error. to_string ()) ?; 668if !trusted ( & handle. dir_metadata (). map_err ( |error| error. to_string ()) ?) { 669return Err ( format! ( 670"Untrusted dependency directory: {}" , 671 directory. display () 672)); 673} 674let source =read_config ( & handle, & path) ?. ok_or_else ( ||{ 675format! ( 676"Dependency directory must contain avril.conf: {}" , 677 directory. display () 678) 679}) ?; 680let base = url:: Url :: from_file_path ( & path). map_err ( |_|"Config paths must be absolute" ) ?; 681let parsed =PackageConfig :: parse ( & source, & base); 682if let Some ( target) =& target{ 683 authority 684. check_local ( target) 685. map_err ( |error| error. to_string ()) ?; 686} 687 parsed 688. map ( |config| config. name ) 689. map_err ( |error| error. render ( & path. to_string_lossy (), & source)) 690} 691 692fn read_config ( handle : & cap_std:: fs:: Dir , path : & Path ) ->Result < Option < String >, String > { 693match handle. symlink_metadata ( "avril.conf" ) { 694Ok ( metadata) if metadata. is_symlink () || !metadata. is_file () =>{ 695return Err ( format! ( 696"Config must be a regular non-symlink file: {}" , 697 path. display () 698)); 699} 700Ok ( metadata) if !trusted ( & metadata) =>{ 701return Err ( format! ( "Untrusted config: {}" , path. display ())); 702} 703Ok ( _) =>{} 704Err ( error) if error. kind () == std:: io:: ErrorKind :: NotFound =>return Ok ( None ), 705Err ( _) =>return Err ( format! ( "Cannot discover config {}" , path. display ())), 706} 707let file =crate :: sources:: open_regular ( handle, "avril.conf" ) 708. map_err ( |_|format! ( "Cannot read config {}" , path. display ())) ?; 709let metadata = file 710. metadata () 711. map_err ( |_|"Cannot inspect config" . to_owned ()) ?; 712if !trusted ( & metadata) { 713return Err ( format! ( "Untrusted config: {}" , path. display ())); 714} 715let mut source =String :: new (); 716 file. take ( 32 * 1024 * 1024 +1 ) 717. read_to_string ( & mut source) 718. map_err ( |_|format! ( "Cannot read config {}" , path. display ())) ?; 719if source. len () >32 * 1024 * 1024 { 720return Err ( "Config is too large" . into ()); 721} 722Ok ( Some ( source)) 723} 724 725pub ( crate ) fn trusted ( metadata : & cap_std:: fs:: Metadata ) ->bool { 726# [ cfg ( unix )] 727{ 728use cap_std:: fs:: MetadataExt ; 729// Root-owned installs are trusted; shared writable directories are not config authorities. 730( metadata. uid () ==0 || metadata. uid () ==unsafe { libc:: geteuid () }) 731 && metadata. mode () & 0o002 ==0 732} 733# [ cfg ( not ( unix ))] 734{ 735let _ = metadata; 736true 737} 738} 739 740/// Whether `path` lies at or below a reserved `.avril/npm` tree. Only the 741/// npm instance tree is skipped during config discovery; every other 742/// `.avril` path keeps its existing behavior. 743fn under_npm_tree ( path : & Path ) ->bool { 744let mut parts = path. components (). filter_map ( |part|match part{ 745 std:: path:: Component :: Normal ( name) => name. to_str (), 746 _ =>None , 747}); 748let mut previous = parts. next (); 749for currentin parts{ 750if previous ==Some ( ".avril" ) && current =="npm" { 751return true ; 752} 753 previous =Some ( current); 754} 755false 756} 757 758pub ( crate ) fn export_key ( specifier : & str , binding : & str ) ->Option < String > { 759if specifier == binding{ 760Some ( "." . into ()) 761} else { 762 specifier 763. strip_prefix ( binding) 764. and_then ( |suffix| suffix. strip_prefix ( '/' )) 765. map ( |suffix|format! ( "./{suffix}" )) 766} 767}