// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.bazel.repository.downloader; import com.google.common.base.Ascii; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Collection; /** HTTP utilities. */ public final class HttpUtils { /** Returns {@code true} if {@code url} is supported by {@link HttpDownloader}. */ public static boolean isUrlSupportedByDownloader(URL url) { return isHttp(url) || isProtocol(url, "file"); } static boolean isHttp(URL url) { return isProtocol(url, "http") || isProtocol(url, "https"); } static boolean isProtocol(URL url, String protocol) { // An implementation should accept uppercase letters as equivalent to lowercase in scheme names // (e.g., allow "HTTP" as well as "http") for the sake of robustness. Quoth RFC3986 § 3.1 return Ascii.equalsIgnoreCase(protocol, url.getProtocol()); } static void checkUrlsArgument(Collection<URL> urls) { Preconditions.checkArgument(!urls.isEmpty(), "urls list empty"); for (URL url : urls) { Preconditions.checkArgument(isUrlSupportedByDownloader(url), "unsupported protocol: %s", url); } } static String getExtension(String path) { int index = path.lastIndexOf('.'); if (index == -1) { return ""; } return Ascii.toLowerCase(path.substring(index + 1)); } static URL getLocation(HttpURLConnection connection) throws IOException { String newLocation = connection.getHeaderField("Location"); if (newLocation == null) { throw new IOException("Remote redirect missing Location."); } URL result = mergeUrls(URI.create(newLocation), connection.getURL()); if (!isHttp(result)) { throw new IOException("Bad Location: " + newLocation); } return result; } private static URL mergeUrls(URI preferred, URL original) throws IOException { // If the Location value provided in a 3xx (Redirection) response does not have a fragment // component, a user agent MUST process the redirection as if the value inherits the fragment // component of the URI reference used to generate the request target (i.e., the redirection // inherits the original reference's fragment, if any). Quoth RFC7231 § 7.1.2 String protocol = MoreObjects.firstNonNull(preferred.getScheme(), original.getProtocol()); String userInfo = preferred.getUserInfo(); String host = preferred.getHost(); int port; if (host == null) { host = original.getHost(); port = original.getPort(); userInfo = original.getUserInfo(); } else { port = preferred.getPort(); if (userInfo == null && host.equals(original.getHost()) && port == original.getPort()) { userInfo = original.getUserInfo(); } } String path = preferred.getPath(); String query = preferred.getQuery(); String fragment = preferred.getFragment(); if (fragment == null) { fragment = original.getRef(); } URL result; try { result = new URI(protocol, userInfo, host, port, path, query, fragment).toURL(); } catch (URISyntaxException | MalformedURLException e) { throw new IOException("Could not merge " + preferred + " into " + original); } return result; } private HttpUtils() {} }