16
16
import static com .google .common .collect .ImmutableList .toImmutableList ;
17
17
import static java .nio .charset .StandardCharsets .ISO_8859_1 ;
18
18
19
+ import com .google .auth .Credentials ;
20
+ import com .google .auto .value .AutoValue ;
19
21
import com .google .common .annotations .VisibleForTesting ;
20
22
import com .google .common .base .Ascii ;
21
23
import com .google .common .base .Preconditions ;
22
24
import com .google .common .base .Strings ;
23
25
import com .google .common .collect .ImmutableList ;
24
26
import com .google .common .collect .ImmutableMap ;
25
27
import 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 ;
26
31
import com .google .devtools .build .lib .events .Event ;
27
32
import 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 ;
28
36
import java .io .BufferedReader ;
29
37
import java .io .IOException ;
30
38
import java .io .Reader ;
38
46
import java .nio .file .Paths ;
39
47
import java .util .Base64 ;
40
48
import java .util .Collection ;
49
+ import java .util .HashMap ;
41
50
import java .util .List ;
42
51
import java .util .Map ;
43
52
import java .util .Objects ;
53
+ import java .util .Optional ;
44
54
import java .util .function .Consumer ;
45
55
import java .util .function .Function ;
46
56
import java .util .regex .Matcher ;
47
57
import java .util .regex .Pattern ;
48
58
import javax .annotation .Nullable ;
59
+ import net .starlark .java .syntax .Location ;
49
60
50
61
/**
51
62
* Helper class for taking URLs and converting them according to an optional config specified by
@@ -59,13 +70,19 @@ public class UrlRewriter {
59
70
private static final ImmutableSet <String > REWRITABLE_SCHEMES = ImmutableSet .of ("http" , "https" );
60
71
61
72
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 ;
63
75
64
76
@ 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 )
66
82
throws UrlRewriterParseException {
67
83
Preconditions .checkNotNull (reader , "UrlRewriterConfig source must be set" );
68
84
this .config = new UrlRewriterConfig (filePathForErrorReporting , reader );
85
+ this .netrcCreds = netrcCreds ;
69
86
70
87
this .rewriter = this ::rewrite ;
71
88
}
@@ -75,89 +92,124 @@ public class UrlRewriter {
75
92
*
76
93
* @param configPath Path to the config file to use. May be null.
77
94
* @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
78
97
*/
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 )
80
103
throws UrlRewriterParseException {
81
104
Consumer <String > log = str -> reporter .handle (Event .info (str ));
82
105
106
+ // "empty" UrlRewriter shouldn't alter auth headers
83
107
if (Strings .isNullOrEmpty (configPath )) {
84
- return new UrlRewriter (log , "" , new StringReader ("" ));
108
+ return new UrlRewriter (log , "" , new StringReader ("" ), null );
85
109
}
86
110
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
+ }
87
117
try (BufferedReader reader = Files .newBufferedReader (Paths .get (configPath ))) {
88
- return new UrlRewriter (log , configPath , reader );
118
+ return new UrlRewriter (log , configPath , reader , creds );
89
119
} catch (IOException e ) {
90
120
throw new UncheckedIOException (e );
91
121
}
92
122
}
93
123
94
124
/**
95
125
* 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.
98
128
*
99
129
* @param urls The input list of {@link URL}s. May be empty.
100
130
* @return The amended lists of URLs.
101
131
*/
102
- public List < URL > amend (List <URL > urls ) {
132
+ public ImmutableList < RewrittenURL > amend (List <URL > urls ) {
103
133
Objects .requireNonNull (urls , "URLS to check must be set but may be empty" );
104
134
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 ());
109
136
}
110
137
111
138
/**
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>.
113
142
*
114
143
* @param urls The input list of {@link URL}s. May be empty.
115
144
* @param authHeaders A map of the URLs and their corresponding auth tokens.
116
145
* @return A map of the updated authentication headers.
117
146
*/
118
147
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 );
122
150
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 ();
125
159
if (userInfo != null ) {
126
160
try {
127
161
String token =
128
162
"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 ));
130
164
} catch (URISyntaxException e ) {
131
165
// If the credentials extraction failed, we're letting bazel try without credentials.
132
166
}
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
+ }
133
185
}
134
186
}
135
187
136
- return authHeadersBuilder . build ( );
188
+ return ImmutableMap . copyOf ( updatedAuthHeaders );
137
189
}
138
190
139
- private ImmutableList <URL > rewrite (URL url ) {
191
+ private ImmutableList <RewrittenURL > rewrite (URL url ) {
140
192
Preconditions .checkNotNull (url );
141
193
142
194
// Cowardly refuse to rewrite non-HTTP(S) urls
143
195
if (REWRITABLE_SCHEMES .stream ()
144
196
.noneMatch (scheme -> Ascii .equalsIgnoreCase (scheme , url .getProtocol ()))) {
145
- return ImmutableList .of (url );
197
+ return ImmutableList .of (RewrittenURL . create ( url , false ) );
146
198
}
147
199
148
- List < URL > rewrittenUrls = applyRewriteRules (url );
200
+ ImmutableList < RewrittenURL > rewrittenUrls = applyRewriteRules (url );
149
201
150
- ImmutableList .Builder <URL > toReturn = ImmutableList .builder ();
202
+ ImmutableList .Builder <RewrittenURL > toReturn = ImmutableList .builder ();
151
203
// Now iterate over the URLs
152
- for (URL consider : rewrittenUrls ) {
204
+ for (RewrittenURL consider : rewrittenUrls ) {
153
205
// If there's an allow entry, add it to the set to return and continue
154
- if (isAllowMatched (consider )) {
206
+ if (isAllowMatched (consider . url () )) {
155
207
toReturn .add (consider );
156
208
continue ;
157
209
}
158
210
159
211
// 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 () )) {
161
213
toReturn .add (consider );
162
214
}
163
215
}
@@ -192,7 +244,7 @@ private static boolean isMatchingHostName(URL url, String host) {
192
244
return host .equals (url .getHost ()) || url .getHost ().endsWith ("." + host );
193
245
}
194
246
195
- private ImmutableList <URL > applyRewriteRules (URL url ) {
247
+ private ImmutableList <RewrittenURL > applyRewriteRules (URL url ) {
196
248
String withoutScheme = url .toString ().substring (url .getProtocol ().length () + 3 );
197
249
198
250
ImmutableSet .Builder <String > rewrittenUrls = ImmutableSet .builder ();
@@ -210,11 +262,12 @@ private ImmutableList<URL> applyRewriteRules(URL url) {
210
262
}
211
263
212
264
if (!matchMade ) {
213
- return ImmutableList .of (url );
265
+ return ImmutableList .of (RewrittenURL . create ( url , false ) );
214
266
}
215
267
216
268
return rewrittenUrls .build ().stream ()
217
269
.map (urlString -> prefixWithProtocol (urlString , url .getProtocol ()))
270
+ .map (plainUrl -> RewrittenURL .create (plainUrl , true ))
218
271
.collect (toImmutableList ());
219
272
}
220
273
@@ -232,8 +285,64 @@ private static URL prefixWithProtocol(String url, String protocol) {
232
285
}
233
286
}
234
287
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
+
235
332
@ Nullable
236
333
public String getAllBlockedMessage () {
237
334
return config .getAllBlockedMessage ();
238
335
}
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
+ }
239
348
}
0 commit comments