HttpConfig.java
- /*
- * Copyright (C) 2008, 2010, Google Inc.
- * Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch> and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
- package org.eclipse.jgit.transport;
- import java.io.IOException;
- import java.net.URISyntaxException;
- import java.text.MessageFormat;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.List;
- import java.util.Set;
- import java.util.function.Supplier;
- import org.eclipse.jgit.annotations.NonNull;
- import org.eclipse.jgit.errors.ConfigInvalidException;
- import org.eclipse.jgit.internal.JGitText;
- import org.eclipse.jgit.lib.Config;
- import org.eclipse.jgit.lib.StoredConfig;
- import org.eclipse.jgit.util.StringUtils;
- import org.eclipse.jgit.util.SystemReader;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- /**
- * A representation of the "http.*" config values in a git
- * {@link org.eclipse.jgit.lib.Config}. git provides for setting values for
- * specific URLs through "http.<url>.*" subsections. git always considers
- * only the initial original URL for such settings, not any redirected URL.
- *
- * @since 4.9
- */
- public class HttpConfig {
- private static final Logger LOG = LoggerFactory.getLogger(HttpConfig.class);
- private static final String FTP = "ftp"; //$NON-NLS-1$
- /** git config section key for http settings. */
- public static final String HTTP = "http"; //$NON-NLS-1$
- /** git config key for the "followRedirects" setting. */
- public static final String FOLLOW_REDIRECTS_KEY = "followRedirects"; //$NON-NLS-1$
- /** git config key for the "maxRedirects" setting. */
- public static final String MAX_REDIRECTS_KEY = "maxRedirects"; //$NON-NLS-1$
- /** git config key for the "postBuffer" setting. */
- public static final String POST_BUFFER_KEY = "postBuffer"; //$NON-NLS-1$
- /** git config key for the "sslVerify" setting. */
- public static final String SSL_VERIFY_KEY = "sslVerify"; //$NON-NLS-1$
- /**
- * git config key for the "userAgent" setting.
- *
- * @since 5.10
- */
- public static final String USER_AGENT = "userAgent"; //$NON-NLS-1$
- /**
- * git config key for the "extraHeader" setting.
- *
- * @since 5.10
- */
- public static final String EXTRA_HEADER = "extraHeader"; //$NON-NLS-1$
- /**
- * git config key for the "cookieFile" setting.
- *
- * @since 5.4
- */
- public static final String COOKIE_FILE_KEY = "cookieFile"; //$NON-NLS-1$
- /**
- * git config key for the "saveCookies" setting.
- *
- * @since 5.4
- */
- public static final String SAVE_COOKIES_KEY = "saveCookies"; //$NON-NLS-1$
- /**
- * Custom JGit config key which holds the maximum number of cookie files to
- * keep in the cache.
- *
- * @since 5.4
- */
- public static final String COOKIE_FILE_CACHE_LIMIT_KEY = "cookieFileCacheLimit"; //$NON-NLS-1$
- private static final int DEFAULT_COOKIE_FILE_CACHE_LIMIT = 10;
- private static final String MAX_REDIRECT_SYSTEM_PROPERTY = "http.maxRedirects"; //$NON-NLS-1$
- private static final int DEFAULT_MAX_REDIRECTS = 5;
- private static final int MAX_REDIRECTS = (new Supplier<Integer>() {
- @Override
- public Integer get() {
- String rawValue = SystemReader.getInstance()
- .getProperty(MAX_REDIRECT_SYSTEM_PROPERTY);
- Integer value = Integer.valueOf(DEFAULT_MAX_REDIRECTS);
- if (rawValue != null) {
- try {
- value = Integer.valueOf(Integer.parseUnsignedInt(rawValue));
- } catch (NumberFormatException e) {
- LOG.warn(MessageFormat.format(
- JGitText.get().invalidSystemProperty,
- MAX_REDIRECT_SYSTEM_PROPERTY, rawValue, value));
- }
- }
- return value;
- }
- }).get().intValue();
- private static final String ENV_HTTP_USER_AGENT = "GIT_HTTP_USER_AGENT"; //$NON-NLS-1$
- /**
- * Config values for http.followRedirect.
- */
- public enum HttpRedirectMode implements Config.ConfigEnum {
- /** Always follow redirects (up to the http.maxRedirects limit). */
- TRUE("true"), //$NON-NLS-1$
- /**
- * Only follow redirects on the initial GET request. This is the
- * default.
- */
- INITIAL("initial"), //$NON-NLS-1$
- /** Never follow redirects. */
- FALSE("false"); //$NON-NLS-1$
- private final String configValue;
- private HttpRedirectMode(String configValue) {
- this.configValue = configValue;
- }
- @Override
- public String toConfigValue() {
- return configValue;
- }
- @Override
- public boolean matchConfigValue(String s) {
- return configValue.equals(s);
- }
- }
- private int postBuffer;
- private boolean sslVerify;
- private HttpRedirectMode followRedirects;
- private int maxRedirects;
- private String userAgent;
- private List<String> extraHeaders;
- private String cookieFile;
- private boolean saveCookies;
- private int cookieFileCacheLimit;
- /**
- * Get the "http.postBuffer" setting
- *
- * @return the value of the "http.postBuffer" setting
- */
- public int getPostBuffer() {
- return postBuffer;
- }
- /**
- * Get the "http.sslVerify" setting
- *
- * @return the value of the "http.sslVerify" setting
- */
- public boolean isSslVerify() {
- return sslVerify;
- }
- /**
- * Get the "http.followRedirects" setting
- *
- * @return the value of the "http.followRedirects" setting
- */
- public HttpRedirectMode getFollowRedirects() {
- return followRedirects;
- }
- /**
- * Get the "http.maxRedirects" setting
- *
- * @return the value of the "http.maxRedirects" setting
- */
- public int getMaxRedirects() {
- return maxRedirects;
- }
- /**
- * Get the "http.userAgent" setting
- *
- * @return the value of the "http.userAgent" setting
- * @since 5.10
- */
- public String getUserAgent() {
- return userAgent;
- }
- /**
- * Get the "http.extraHeader" setting
- *
- * @return the value of the "http.extraHeader" setting
- * @since 5.10
- */
- @NonNull
- public List<String> getExtraHeaders() {
- return extraHeaders == null ? Collections.emptyList() : extraHeaders;
- }
- /**
- * Get the "http.cookieFile" setting
- *
- * @return the value of the "http.cookieFile" setting
- *
- * @since 5.4
- */
- public String getCookieFile() {
- return cookieFile;
- }
- /**
- * Get the "http.saveCookies" setting
- *
- * @return the value of the "http.saveCookies" setting
- *
- * @since 5.4
- */
- public boolean getSaveCookies() {
- return saveCookies;
- }
- /**
- * Get the "http.cookieFileCacheLimit" setting (gives the maximum number of
- * cookie files to keep in the LRU cache)
- *
- * @return the value of the "http.cookieFileCacheLimit" setting
- *
- * @since 5.4
- */
- public int getCookieFileCacheLimit() {
- return cookieFileCacheLimit;
- }
- /**
- * Creates a new {@link org.eclipse.jgit.transport.HttpConfig} tailored to
- * the given {@link org.eclipse.jgit.transport.URIish}.
- *
- * @param config
- * to read the {@link org.eclipse.jgit.transport.HttpConfig} from
- * @param uri
- * to get the configuration values for
- */
- public HttpConfig(Config config, URIish uri) {
- init(config, uri);
- }
- /**
- * Creates a {@link org.eclipse.jgit.transport.HttpConfig} that reads values
- * solely from the user config.
- *
- * @param uri
- * to get the configuration values for
- */
- public HttpConfig(URIish uri) {
- StoredConfig userConfig = null;
- try {
- userConfig = SystemReader.getInstance().getUserConfig();
- } catch (IOException | ConfigInvalidException e) {
- // Log it and then work with default values.
- LOG.error(e.getMessage(), e);
- init(new Config(), uri);
- return;
- }
- init(userConfig, uri);
- }
- private void init(Config config, URIish uri) {
- // Set defaults from the section first
- int postBufferSize = config.getInt(HTTP, POST_BUFFER_KEY,
- 1 * 1024 * 1024);
- boolean sslVerifyFlag = config.getBoolean(HTTP, SSL_VERIFY_KEY, true);
- HttpRedirectMode followRedirectsMode = config.getEnum(
- HttpRedirectMode.values(), HTTP, null,
- FOLLOW_REDIRECTS_KEY, HttpRedirectMode.INITIAL);
- int redirectLimit = config.getInt(HTTP, MAX_REDIRECTS_KEY,
- MAX_REDIRECTS);
- if (redirectLimit < 0) {
- redirectLimit = MAX_REDIRECTS;
- }
- String agent = config.getString(HTTP, null, USER_AGENT);
- if (agent != null) {
- agent = UserAgent.clean(agent);
- }
- userAgent = agent;
- String[] headers = config.getStringList(HTTP, null, EXTRA_HEADER);
- // https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpextraHeader
- // "an empty value will reset the extra headers to the empty list."
- int start = findLastEmpty(headers) + 1;
- if (start > 0) {
- headers = Arrays.copyOfRange(headers, start, headers.length);
- }
- extraHeaders = Arrays.asList(headers);
- cookieFile = config.getString(HTTP, null, COOKIE_FILE_KEY);
- saveCookies = config.getBoolean(HTTP, SAVE_COOKIES_KEY, false);
- cookieFileCacheLimit = config.getInt(HTTP, COOKIE_FILE_CACHE_LIMIT_KEY,
- DEFAULT_COOKIE_FILE_CACHE_LIMIT);
- String match = findMatch(config.getSubsections(HTTP), uri);
- if (match != null) {
- // Override with more specific items
- postBufferSize = config.getInt(HTTP, match, POST_BUFFER_KEY,
- postBufferSize);
- sslVerifyFlag = config.getBoolean(HTTP, match, SSL_VERIFY_KEY,
- sslVerifyFlag);
- followRedirectsMode = config.getEnum(HttpRedirectMode.values(),
- HTTP, match, FOLLOW_REDIRECTS_KEY, followRedirectsMode);
- int newMaxRedirects = config.getInt(HTTP, match, MAX_REDIRECTS_KEY,
- redirectLimit);
- if (newMaxRedirects >= 0) {
- redirectLimit = newMaxRedirects;
- }
- String uriSpecificUserAgent = config.getString(HTTP, match,
- USER_AGENT);
- if (uriSpecificUserAgent != null) {
- userAgent = UserAgent.clean(uriSpecificUserAgent);
- }
- String[] uriSpecificExtraHeaders = config.getStringList(HTTP, match,
- EXTRA_HEADER);
- if (uriSpecificExtraHeaders.length > 0) {
- start = findLastEmpty(uriSpecificExtraHeaders) + 1;
- if (start > 0) {
- uriSpecificExtraHeaders = Arrays.copyOfRange(
- uriSpecificExtraHeaders, start,
- uriSpecificExtraHeaders.length);
- }
- extraHeaders = Arrays.asList(uriSpecificExtraHeaders);
- }
- String urlSpecificCookieFile = config.getString(HTTP, match,
- COOKIE_FILE_KEY);
- if (urlSpecificCookieFile != null) {
- cookieFile = urlSpecificCookieFile;
- }
- saveCookies = config.getBoolean(HTTP, match, SAVE_COOKIES_KEY,
- saveCookies);
- }
- // Environment overrides config
- agent = SystemReader.getInstance().getenv(ENV_HTTP_USER_AGENT);
- if (!StringUtils.isEmptyOrNull(agent)) {
- userAgent = UserAgent.clean(agent);
- }
- postBuffer = postBufferSize;
- sslVerify = sslVerifyFlag;
- followRedirects = followRedirectsMode;
- maxRedirects = redirectLimit;
- }
- private int findLastEmpty(String[] values) {
- for (int i = values.length - 1; i >= 0; i--) {
- if (values[i] == null) {
- return i;
- }
- }
- return -1;
- }
- /**
- * Determines the best match from a set of subsection names (representing
- * prefix URLs) for the given {@link URIish}.
- *
- * @param names
- * to match against the {@code uri}
- * @param uri
- * to find a match for
- * @return the best matching subsection name, or {@code null} if no
- * subsection matches
- */
- private String findMatch(Set<String> names, URIish uri) {
- String bestMatch = null;
- int bestMatchLength = -1;
- boolean withUser = false;
- String uPath = uri.getPath();
- boolean hasPath = !StringUtils.isEmptyOrNull(uPath);
- if (hasPath) {
- uPath = normalize(uPath);
- if (uPath == null) {
- // Normalization failed; warning was logged.
- return null;
- }
- }
- for (String s : names) {
- try {
- URIish candidate = new URIish(s);
- // Scheme and host must match case-insensitively
- if (!compare(uri.getScheme(), candidate.getScheme())
- || !compare(uri.getHost(), candidate.getHost())) {
- continue;
- }
- // Ports must match after default ports have been substituted
- if (defaultedPort(uri.getPort(),
- uri.getScheme()) != defaultedPort(candidate.getPort(),
- candidate.getScheme())) {
- continue;
- }
- // User: if present in candidate, must match
- boolean hasUser = false;
- if (candidate.getUser() != null) {
- if (!candidate.getUser().equals(uri.getUser())) {
- continue;
- }
- hasUser = true;
- }
- // Path: prefix match, longer is better
- String cPath = candidate.getPath();
- int matchLength = -1;
- if (StringUtils.isEmptyOrNull(cPath)) {
- matchLength = 0;
- } else {
- if (!hasPath) {
- continue;
- }
- // Paths can match only on segments
- matchLength = segmentCompare(uPath, cPath);
- if (matchLength < 0) {
- continue;
- }
- }
- // A longer path match is always preferred even over a user
- // match. If the path matches are equal, a match with user wins
- // over a match without user.
- if (matchLength > bestMatchLength
- || (!withUser && hasUser && matchLength >= 0
- && matchLength == bestMatchLength)) {
- bestMatch = s;
- bestMatchLength = matchLength;
- withUser = hasUser;
- }
- } catch (URISyntaxException e) {
- LOG.warn(MessageFormat
- .format(JGitText.get().httpConfigInvalidURL, s));
- }
- }
- return bestMatch;
- }
- private boolean compare(String a, String b) {
- if (a == null) {
- return b == null;
- }
- return a.equalsIgnoreCase(b);
- }
- private int defaultedPort(int port, String scheme) {
- if (port >= 0) {
- return port;
- }
- if (FTP.equalsIgnoreCase(scheme)) {
- return 21;
- } else if (HTTP.equalsIgnoreCase(scheme)) {
- return 80;
- } else {
- return 443; // https
- }
- }
- static int segmentCompare(String uriPath, String m) {
- // Precondition: !uriPath.isEmpty() && !m.isEmpty(),and u must already
- // be normalized
- String matchPath = normalize(m);
- if (matchPath == null || !uriPath.startsWith(matchPath)) {
- return -1;
- }
- // We can match only on a segment boundary: either both paths are equal,
- // or if matchPath does not end in '/', there is a '/' in uriPath right
- // after the match.
- int uLength = uriPath.length();
- int mLength = matchPath.length();
- if (mLength == uLength || matchPath.charAt(mLength - 1) == '/'
- || (mLength < uLength && uriPath.charAt(mLength) == '/')) {
- return mLength;
- }
- return -1;
- }
- static String normalize(String path) {
- // C-git resolves . and .. segments
- int i = 0;
- int length = path.length();
- StringBuilder builder = new StringBuilder(length);
- builder.append('/');
- if (length > 0 && path.charAt(0) == '/') {
- i = 1;
- }
- while (i < length) {
- int slash = path.indexOf('/', i);
- if (slash < 0) {
- slash = length;
- }
- if (slash == i || (slash == i + 1 && path.charAt(i) == '.')) {
- // Skip /. or also double slashes
- } else if (slash == i + 2 && path.charAt(i) == '.'
- && path.charAt(i + 1) == '.') {
- // Remove previous segment if we have "/.."
- int l = builder.length() - 2; // Skip terminating slash.
- while (l >= 0 && builder.charAt(l) != '/') {
- l--;
- }
- if (l < 0) {
- LOG.warn(MessageFormat.format(
- JGitText.get().httpConfigCannotNormalizeURL, path));
- return null;
- }
- builder.setLength(l + 1);
- } else {
- // Include the slash, if any
- builder.append(path, i, Math.min(length, slash + 1));
- }
- i = slash + 1;
- }
- if (builder.length() > 1 && builder.charAt(builder.length() - 1) == '/'
- && length > 0 && path.charAt(length - 1) != '/') {
- // . or .. normalization left a trailing slash when the original
- // path had none at the end
- builder.setLength(builder.length() - 1);
- }
- return builder.toString();
- }
- }