1616import  static  com .google .common .collect .ImmutableList .toImmutableList ;
1717import  static  java .nio .charset .StandardCharsets .ISO_8859_1 ;
1818
19+ import  com .google .auth .Credentials ;
20+ import  com .google .auto .value .AutoValue ;
1921import  com .google .common .annotations .VisibleForTesting ;
2022import  com .google .common .base .Ascii ;
2123import  com .google .common .base .Preconditions ;
2224import  com .google .common .base .Strings ;
2325import  com .google .common .collect .ImmutableList ;
2426import  com .google .common .collect .ImmutableMap ;
2527import  com .google .common .collect .ImmutableSet ;
28+ import  com .google .devtools .build .lib .authandtls .Netrc ;
29+ import  com .google .devtools .build .lib .authandtls .NetrcCredentials ;
30+ import  com .google .devtools .build .lib .authandtls .NetrcParser ;
2631import  com .google .devtools .build .lib .events .Event ;
2732import  com .google .devtools .build .lib .events .Reporter ;
33+ import  com .google .devtools .build .lib .util .OS ;
34+ import  com .google .devtools .build .lib .vfs .FileSystem ;
35+ import  com .google .devtools .build .lib .vfs .Path ;
2836import  java .io .BufferedReader ;
2937import  java .io .IOException ;
3038import  java .io .Reader ;
3846import  java .nio .file .Paths ;
3947import  java .util .Base64 ;
4048import  java .util .Collection ;
49+ import  java .util .HashMap ;
4150import  java .util .List ;
4251import  java .util .Map ;
4352import  java .util .Objects ;
53+ import  java .util .Optional ;
4454import  java .util .function .Consumer ;
4555import  java .util .function .Function ;
4656import  java .util .regex .Matcher ;
4757import  java .util .regex .Pattern ;
4858import  javax .annotation .Nullable ;
59+ import  net .starlark .java .syntax .Location ;
4960
5061/** 
5162 * Helper class for taking URLs and converting them according to an optional config specified by 
@@ -59,13 +70,19 @@ public class UrlRewriter {
5970  private  static  final  ImmutableSet <String > REWRITABLE_SCHEMES  = ImmutableSet .of ("http" , "https" );
6071
6172  private  final  UrlRewriterConfig  config ;
62-   private  final  Function <URL , List <URL >> rewriter ;
73+   private  final  Function <URL , List <RewrittenURL >> rewriter ;
74+   @ Nullable  private  final  Credentials  netrcCreds ;
6375
6476  @ VisibleForTesting 
65-   UrlRewriter (Consumer <String > log , String  filePathForErrorReporting , Reader  reader )
77+   UrlRewriter (
78+       Consumer <String > log ,
79+       String  filePathForErrorReporting ,
80+       Reader  reader ,
81+       @ Nullable  Credentials  netrcCreds )
6682      throws  UrlRewriterParseException  {
6783    Preconditions .checkNotNull (reader , "UrlRewriterConfig source must be set" );
6884    this .config  = new  UrlRewriterConfig (filePathForErrorReporting , reader );
85+     this .netrcCreds  = netrcCreds ;
6986
7087    this .rewriter  = this ::rewrite ;
7188  }
@@ -75,89 +92,124 @@ public class UrlRewriter {
7592   * 
7693   * @param configPath Path to the config file to use. May be null. 
7794   * @param reporter Used for logging when URLs are rewritten. 
95+    * @param clientEnv a map of the current Bazel command's environment 
96+    * @param fileSystem the Blaze file system 
7897   */ 
79-   public  static  UrlRewriter  getDownloaderUrlRewriter (String  configPath , Reporter  reporter )
98+   public  static  UrlRewriter  getDownloaderUrlRewriter (
99+       String  configPath ,
100+       Reporter  reporter ,
101+       ImmutableMap <String , String > clientEnv ,
102+       FileSystem  fileSystem )
80103      throws  UrlRewriterParseException  {
81104    Consumer <String > log  = str  -> reporter .handle (Event .info (str ));
82105
106+     // "empty" UrlRewriter shouldn't alter auth headers 
83107    if  (Strings .isNullOrEmpty (configPath )) {
84-       return  new  UrlRewriter (log , "" , new  StringReader ("" ));
108+       return  new  UrlRewriter (log , "" , new  StringReader ("" ),  null );
85109    }
86110
111+     Credentials  creds  = null ;
112+     try  {
113+       creds  = newCredentialsFromNetrc (clientEnv , fileSystem );
114+     } catch  (UrlRewriterParseException  e ) {
115+       // If the credentials extraction failed, we're letting bazel try without credentials. 
116+     }
87117    try  (BufferedReader  reader  = Files .newBufferedReader (Paths .get (configPath ))) {
88-       return  new  UrlRewriter (log , configPath , reader );
118+       return  new  UrlRewriter (log , configPath , reader ,  creds );
89119    } catch  (IOException  e ) {
90120      throw  new  UncheckedIOException (e );
91121    }
92122  }
93123
94124  /** 
95125   * Rewrites {@code urls} using the configuration provided to {@link 
96-    * #getDownloaderUrlRewriter(String, Reporter)}. The returned list of URLs may be empty if the  
97-    * configuration used blocks all the input URLs. 
126+    * #getDownloaderUrlRewriter(String, Reporter, ImmutableMap, FileSystem )}. The returned list of 
127+    * URLs may be empty if the  configuration used blocks all the input URLs. 
98128   * 
99129   * @param urls The input list of {@link URL}s. May be empty. 
100130   * @return The amended lists of URLs. 
101131   */ 
102-   public  List < URL > amend (List <URL > urls ) {
132+   public  ImmutableList < RewrittenURL > amend (List <URL > urls ) {
103133    Objects .requireNonNull (urls , "URLS to check must be set but may be empty" );
104134
105-     ImmutableList <URL > rewritten  =
106-         urls .stream ().map (rewriter ).flatMap (Collection ::stream ).collect (toImmutableList ());
107- 
108-     return  rewritten ;
135+     return  urls .stream ().map (rewriter ).flatMap (Collection ::stream ).collect (toImmutableList ());
109136  }
110137
111138  /** 
112-    * Updates {@code authHeaders} using the userInfo available in the provided {@code urls}. 
139+    * Updates {@code authHeaders} using the userInfo available in the provided {@code urls}. Note 
140+    * that if the same url is present in both {@code authHeaders} and <b>download config</b> then it 
141+    * will be overridden with the value from <b>download config</b>. 
113142   * 
114143   * @param urls The input list of {@link URL}s. May be empty. 
115144   * @param authHeaders A map of the URLs and their corresponding auth tokens. 
116145   * @return A map of the updated authentication headers. 
117146   */ 
118147  public  Map <URI , Map <String , String >> updateAuthHeaders (
119-       List <URL > urls , Map <URI , Map <String , String >> authHeaders ) {
120-     ImmutableMap .Builder <URI , Map <String , String >> authHeadersBuilder  =
121-         ImmutableMap .<URI , Map <String , String >>builder ().putAll (authHeaders );
148+       List <RewrittenURL > urls , Map <URI , Map <String , String >> authHeaders ) {
149+     Map <URI , Map <String , String >> updatedAuthHeaders  = new  HashMap <>(authHeaders );
122150
123-     for  (URL  url  : urls ) {
124-       String  userInfo  = url .getUserInfo ();
151+     for  (RewrittenURL  url  : urls ) {
152+       // if URL was not re-written by UrlRewriter in first place, we should not attach auth headers 
153+       // to it 
154+       if  (!url .rewritten ()) {
155+         continue ;
156+       }
157+ 
158+       String  userInfo  = url .url ().getUserInfo ();
125159      if  (userInfo  != null ) {
126160        try  {
127161          String  token  =
128162              "Basic "  + Base64 .getEncoder ().encodeToString (userInfo .getBytes (ISO_8859_1 ));
129-           authHeadersBuilder .put (url .toURI (), ImmutableMap .of ("Authorization" , token ));
163+           updatedAuthHeaders .put (url . url () .toURI (), ImmutableMap .of ("Authorization" , token ));
130164        } catch  (URISyntaxException  e ) {
131165          // If the credentials extraction failed, we're letting bazel try without credentials. 
132166        }
167+       } else  if  (this .netrcCreds  != null ) {
168+         try  {
169+           Map <String , List <String >> urlAuthHeaders  =
170+               this .netrcCreds .getRequestMetadata (url .url ().toURI ());
171+           if  (urlAuthHeaders  == null  || urlAuthHeaders .isEmpty ()) {
172+             continue ;
173+           }
174+           // there could be multiple Auth headers, take the first one 
175+           Map .Entry <String , List <String >> firstAuthHeader  =
176+               urlAuthHeaders .entrySet ().stream ().findFirst ().get ();
177+           if  (firstAuthHeader .getValue () != null  && !firstAuthHeader .getValue ().isEmpty ()) {
178+             updatedAuthHeaders .put (
179+                 url .url ().toURI (),
180+                 ImmutableMap .of (firstAuthHeader .getKey (), firstAuthHeader .getValue ().get (0 )));
181+           }
182+         } catch  (URISyntaxException  | IOException  e ) {
183+           // If the credentials extraction failed, we're letting bazel try without credentials. 
184+         }
133185      }
134186    }
135187
136-     return  authHeadersBuilder . build ( );
188+     return  ImmutableMap . copyOf ( updatedAuthHeaders );
137189  }
138190
139-   private  ImmutableList <URL > rewrite (URL  url ) {
191+   private  ImmutableList <RewrittenURL > rewrite (URL  url ) {
140192    Preconditions .checkNotNull (url );
141193
142194    // Cowardly refuse to rewrite non-HTTP(S) urls 
143195    if  (REWRITABLE_SCHEMES .stream ()
144196        .noneMatch (scheme  -> Ascii .equalsIgnoreCase (scheme , url .getProtocol ()))) {
145-       return  ImmutableList .of (url );
197+       return  ImmutableList .of (RewrittenURL . create ( url ,  false ) );
146198    }
147199
148-     List < URL > rewrittenUrls  = applyRewriteRules (url );
200+     ImmutableList < RewrittenURL > rewrittenUrls  = applyRewriteRules (url );
149201
150-     ImmutableList .Builder <URL > toReturn  = ImmutableList .builder ();
202+     ImmutableList .Builder <RewrittenURL > toReturn  = ImmutableList .builder ();
151203    // Now iterate over the URLs 
152-     for  (URL  consider  : rewrittenUrls ) {
204+     for  (RewrittenURL  consider  : rewrittenUrls ) {
153205      // If there's an allow entry, add it to the set to return and continue 
154-       if  (isAllowMatched (consider )) {
206+       if  (isAllowMatched (consider . url () )) {
155207        toReturn .add (consider );
156208        continue ;
157209      }
158210
159211      // If there's no block that matches the domain, add it to the set to return and continue 
160-       if  (!isBlockMatched (consider )) {
212+       if  (!isBlockMatched (consider . url () )) {
161213        toReturn .add (consider );
162214      }
163215    }
@@ -192,7 +244,7 @@ private static boolean isMatchingHostName(URL url, String host) {
192244    return  host .equals (url .getHost ()) || url .getHost ().endsWith ("."  + host );
193245  }
194246
195-   private  ImmutableList <URL > applyRewriteRules (URL  url ) {
247+   private  ImmutableList <RewrittenURL > applyRewriteRules (URL  url ) {
196248    String  withoutScheme  = url .toString ().substring (url .getProtocol ().length () + 3 );
197249
198250    ImmutableSet .Builder <String > rewrittenUrls  = ImmutableSet .builder ();
@@ -210,11 +262,12 @@ private ImmutableList<URL> applyRewriteRules(URL url) {
210262    }
211263
212264    if  (!matchMade ) {
213-       return  ImmutableList .of (url );
265+       return  ImmutableList .of (RewrittenURL . create ( url ,  false ) );
214266    }
215267
216268    return  rewrittenUrls .build ().stream ()
217269        .map (urlString  -> prefixWithProtocol (urlString , url .getProtocol ()))
270+         .map (plainUrl  -> RewrittenURL .create (plainUrl , true ))
218271        .collect (toImmutableList ());
219272  }
220273
@@ -232,8 +285,64 @@ private static URL prefixWithProtocol(String url, String protocol) {
232285    }
233286  }
234287
288+   /** 
289+    * Create a new {@link Credentials} object by parsing the .netrc file with following order to 
290+    * search it: 
291+    * 
292+    * <ol> 
293+    *   <li>If environment variable $NETRC exists, use it as the path to the .netrc file 
294+    *   <li>Fallback to $HOME/.netrc or $USERPROFILE/.netrc 
295+    * </ol> 
296+    * 
297+    * @return the {@link Credentials} object or {@code null} if there is no .netrc file. 
298+    * @throws UrlRewriterParseException in case the credentials can't be constructed. 
299+    */ 
300+   // TODO : consider re-using RemoteModule.newCredentialsFromNetrc 
301+   @ VisibleForTesting 
302+   static  Credentials  newCredentialsFromNetrc (Map <String , String > clientEnv , FileSystem  fileSystem )
303+       throws  UrlRewriterParseException  {
304+     final  Optional <String > homeDir ;
305+     if  (OS .getCurrent () == OS .WINDOWS ) {
306+       homeDir  = Optional .ofNullable (clientEnv .get ("USERPROFILE" ));
307+     } else  {
308+       homeDir  = Optional .ofNullable (clientEnv .get ("HOME" ));
309+     }
310+     String  netrcFileString  =
311+         Optional .ofNullable (clientEnv .get ("NETRC" ))
312+             .orElseGet (() -> homeDir .map (home  -> home  + "/.netrc" ).orElse (null ));
313+     if  (netrcFileString  == null ) {
314+       return  null ;
315+     }
316+     Location  location  = Location .fromFileLineColumn (netrcFileString , 0 , 0 );
317+ 
318+     Path  netrcFile  = fileSystem .getPath (netrcFileString );
319+     if  (netrcFile .exists ()) {
320+       try  {
321+         Netrc  netrc  = NetrcParser .parseAndClose (netrcFile .getInputStream ());
322+         return  new  NetrcCredentials (netrc );
323+       } catch  (IOException  e ) {
324+         throw  new  UrlRewriterParseException (
325+             "Failed to parse "  + netrcFile .getPathString () + ": "  + e .getMessage (), location );
326+       }
327+     } else  {
328+       return  null ;
329+     }
330+   }
331+ 
235332  @ Nullable 
236333  public  String  getAllBlockedMessage () {
237334    return  config .getAllBlockedMessage ();
238335  }
336+ 
337+   /** Holds the URL along with meta-info, such as whether URL was re-written or not. */ 
338+   @ AutoValue 
339+   public  abstract  static  class  RewrittenURL  {
340+     static  RewrittenURL  create (URL  url , boolean  rewritten ) {
341+       return  new  AutoValue_UrlRewriter_RewrittenURL (url , rewritten );
342+     }
343+ 
344+     abstract  URL  url ();
345+ 
346+     abstract  boolean  rewritten ();
347+   }
239348}
0 commit comments